重命名键时选择值



我想实现一个带有一些键重映射的拾取选项。像这样:

let demo: {
one$: number
two$: string
}
let result = pick(demo, ['one'])
// result equals { one: number }

我想要选择没有$后缀的对象键

的基本选择没有键映射是这样的:

function pick<T, K extends keyof T>(x: T, keys: K[]): Pick<T, K> {
// ...
}

我可以加上这个后缀:

type WithStreamSuffix<T> = { [P in keyof T & string as `${P}$`]: T[P] }

但我没能把两者混在一起。也许使用去掉后缀的键重映射会更容易,但我不知道怎么做。

为了使类型推断以您想要的方式为pick()的调用者工作,您可能需要将K作为键的类型,最后已经删除了"$",然后在执行Pick<T, ...>时将"$"重新添加。像这样输入:

type DropDollar<T> = T extends `${infer F}$` ? F : never;
function pick<T, K extends DropDollar<keyof T>>(
x: T, keys: K[]): Pick<T, Extract<`${K}$`, keyof T>>;

DropDollar<T>类型使用模板文字类型的条件类型推断来获得最终"$"之前的所有内容。然后当我们执行Pick时,我们需要像`${K}$`一样将其添加回去。注意,编译器无法理解像`${DropDollar<keyof T>}$` extends keyof T这样的高阶类型约束对于未指定的泛型T。如果你只写Pick<T, `${K}$`>,你会得到一个错误。相反,您需要通过编写Extract<`${K}$`,keyof T>来说服它。Extract<T, U>实用程序类型通常可以这样使用:您希望在期望U的地方使用T。如果编译器没有看到T extends U,它肯定会看到Extract<T, U> extends U.


无论如何,实现可能是:

function pick(x: any, keys: string[]) {
return keys.reduce((ret, k) => (ret[k + "$"] = x[k + "$"], ret), {} as any);
}

我在这里使用了一个单调用签名函数重载来分离对调用者的强类型关注和对实现者的强类型关注。同样,编译器将无法根据未指定的通用TK类型跟踪实现的正确性,否则您只会得到错误。将实现类型放宽到anystring[]使其编译没有错误。它不是特别的类型安全,所以你需要再次检查你写的是否正确。但是如果你写得正确,那么调用者就会从强类型中受益。

现在您可以验证它的行为是否符合预期:

let demo = { one$: 1, two$: "two" };
let result = pick(demo, ['one']);
console.log(result); // {one$: 1}

看起来不错。

Playground链接到代码