我在打字稿中使用泛型时遇到一些问题。
我想使用泛型基于另一种对象类型创建一个对象。 我的出发点是 来自 TS 的文档
这是我到目前为止所想到的:
type ClickableItem<T extends Record<string, any>, K extends keyof T> = {
label: string;
key: K;
render?: (value: T[K]) => string;
// The value (argument) type here should reflect the type from the selected key
};
如果我尝试在对象中使用此 ClickableItem 类型,我会得到所有可能类型值的
render
并集的参数类型,例如:
type Example = {
a: string,
b: number
}
const item = {
label: 'x',
key: 'a',
render: (value) => value //ERROR HERE
} satisfies ClickableItem<Example, keyof Example>
渲染方法被错误地推断为键入
string | number
,即使键 a
专门设置为键入 string
在前面的示例中,方法 render 的类型应该是
(value: string)=> string
而不是推断类型 (value: number | string)=> string
。
这是 ts Playground 链接,包括 TS 文档中的原始示例和我之前提到的测试实现
{
label: 'x',
key: 'a',
render: (value) => value
}
不是
ClickableItem<Example, keyof Example>
的子类型,所以它不会在这里satisfy
。
ClickableItem<Example, keyof Example>
扩展为这种类型:
type MyType = {
label: string;
key: 'a' | 'b' | 'c' | 'd';
render?: (value: string | number) => string;
};
换句话说,您的代码与:
const itemFromSatisfies = {
label: 'x',
key: 'a',
render: (value) => value //ERROR HERE
} satisfies {
label: string;
key: 'a' | 'b' | 'c' | 'd';
render?: (value: string | number) => string;
}
请注意
key
和 render
函数之间没有任何联系。这实际上已经丢失了,因为您将 keyof Example
作为 K
传入,因此您得到了一种适用于 T
的all 键的对象类型
相反,您需要一个联盟,每个属性都有一名成员。
type ClickableItemUnion<T extends Record<string, any>> = {
[K in keyof T]: ClickableItem<T, K>
}[keyof T]
type MyTest = ClickableItemUnion<Example>
// ClickableItem<Example, 'a'> | ClickableItem<Example, 'b'>
此映射类型为
T
的每个属性创建一个并集,其中 key
和 render
属性可以一次强配对一个。
现在你这样做,它就会起作用:
const itemFromUnion = {
label: 'x',
key: 'a',
render: (value) => value
} satisfies ClickableItemUnion<Example>
这是因为该对象与联合体中的一个成员匹配,其中
key
是 'a'
并且 render
函数接受 string
。
因此,考虑到所有这些,我们可以通过使用从一开始就产生联合的映射类型来简化它:
type ClickableItem<
T extends Record<string, any>,
K extends keyof T
> = {
[P in K]: {
label: string;
key: P;
render?: (value: T[P]) => string;
}
}[K];
此类型映射所请求的任何键的并集,并为每个键生成并集。
所以现在:
type MyType = ClickableItem<Example, keyof Example>
等同于:
type MyType = {
label: string;
key: "a";
render?: ((value: string) => string) | undefined;
} | {
label: string;
key: "b";
render?: ((value: number) => string) | undefined;
}
因此可以在任何问题的
satisfies
约束中使用:
const item = { // fine
label: 'x',
key: 'a',
render: (value) => value
} satisfies ClickableItem<Example, keyof Example>