问题描述
我的目标是创建一个名为getFields
的函数。该函数具有通用的<T>
和参数...fields: Array<keyof T>
。我希望此函数返回一个函数,当给定类型为<T>
的对象时,该函数将返回仅具有...fields
中命名的属性的简化对象。
以下是一种帮助程序类型和我的getFields
实现:
type SubObj<T,S extends Array<keyof T>> = Pick<
T,keyof { [K in S[number]]: K extends keyof T ? K : never }
>;
export function getFields<T extends Record<string,unkNown>>(
...fields: Array<keyof T>
): (obj: T) => SubObj<T,typeof fields> {
return (obj: T) =>
Object.fromEntries(fields.map((field) => [field,obj[field]])) as SubObj<
T,typeof fields
>;
}
我用以下代码测试了此实现:
type A = {
a: string;
b: string;
c: string;
};
const b = getFields<A>('a','c')({ a: '',b: '',c: '' });
但是,当我看着typeof
b
时,它就是Pick<A,"a" | "b" | "c">
。我真正想要的是Pick<A,"a" | "c">
。
我已经尝试了很多事情来使这项工作达到预期的效果,但是唯一的成功是添加了第二个通用参数,这需要我将代码更改为此:
const b = getFields<A,['a','c']>('a',c: '' });
这对我来说太多余了,无法接受。
在这一点上,我认为我已经达到了TypeScript功能的极限,因为我想不出任何其他方法来实现自己想要的东西。
使用TypeScript甚至可以做到吗?如果是这样,我该怎么办?
解决方法
TypeScript当前不支持部分类型参数推断(请参见microsoft/TypeScript#26242)。如果在类型/函数中具有多个类型参数,则需要显式地指定它们,或者让它们全部被推断。没有办法指定一个并让另一个推断出来。如果您想走这条路,则有一些解决方法,但是此问题将成为其他此类问题的重复,我可以例如指向this answer进行操作。
不过,我要退一步说,这里的示例代码可能以更简单的方式表达,根本不需要任何人指定任何类型。考虑一下:
export function getFields<K extends PropertyKey>(
...fields: K[]
) {
return <T extends Record<K,unknown>>(obj: T) =>
Object.fromEntries(fields.map((field) => [field,obj[field]])) as Pick<T,K>;
}
在这里,我们将从T
的呼叫签名中完全删除对象类型getFields()
。 getFields()
唯一关心的就是获取类似键的参数的列表。然后,它返回一个也是通用的函数,并且 this 关心对象类型T
,并用{{ 1}}。因此,K
和T
都是不可推断的:
K
由于const b = getFields('a','c')({ a: '',b: '',c: '' });
/* const b: Pick<{
a: string;
b: string;
c: string;
},"a" | "c"> */
的返回值是通用函数,因此它可以用于getFields()
以外的类型,例如:
A
但是如果您给它一些不适当的地方,还是应该出错:
const c = getFields('a','c')({ a: 1,b: 2,c: 3 }) // {a: number,c: number}
如果您真的很想指定const d = getFields('a','c')({ a: 1 }) // error! property 'c' is missing
,则可以在调用return函数时做到这一点:
T
如果出于某些原因确实想要在开头指定const e = getFields('a','c')<A>({ a: 1,c: 3 }); // error! number is not string
并推断T
,则需要使用一种变通办法来进行部分类型推断,例如更多的咖喱:
K