请参阅以下代码:
interface X {
a: string;
b: number;
c: boolean;
}
type A = {
prop: keyof X;
value: X[keyof X];
}
const a: A = { prop: 'a', value: 'a' }; // ok
const b: A = { prop: 'a', value: 1 }; // not ok, value should be string, because X['a'] is string
const c: A = { prop: 'b', value: 1 }; // ok
const d: A = { prop: 'c', value: 1 }; // not ok, value should be boolean, because X['c'] is string
这里,如果prop
是"a"
,number
是"b"
,boolean
是"c"
,我希望.value
属性的类型是string
,但在所有情况下都是string|number|boolean
,因为keyof X
可以针对type A
的每次使用引用不同的密钥。如何让它两次引用同一属性,而不让它显式地将其输入到A
的泛型参数中?
我觉得我应该用infer
她,但我不确定怎么用,而且我可能在那里走错了路。
您希望A
是keyof X
中每个K
的{prop: K; value: X[K]}
的并集,如下所示:
type A = {
prop: "a";
value: string;
} | {
prop: "b";
value: number;
} | {
prop: "c";
value: boolean;
};
在该并集的每个元素中,prop
类型和value
类型之间都有关联,这禁止您从X
:的不同成员分配prop
和value
类型
const a: A = { prop: 'a', value: 'a' }; // ok
const b: A = { prop: 'a', value: 1 }; // error
const c: A = { prop: 'b', value: 1 }; // ok
const d: A = { prop: 'c', value: 1 }; // error
您还可以通过以下几种方式让编译器以编程方式为您计算此值,例如构建一个立即索引到的映射类型:
type A = { [K in keyof X]-?: {
prop: K;
value: X[K];
} }[keyof X];
可以通过IntelliSense验证A
的上述定义是等效的,但现在如果修改X
,A
将自动更新。
游乐场链接到代码
如果没有泛型,我不相信你能做到这一点。现在是:
type A<K extends keyof X> = {
prop: K;
value: X[K];
}
好消息是,当您为显式给定类型的变量赋值时,只需要指定泛型参数,如下所示:
const a: A<'a'> = { prop: 'a', value: 'a' };
而且,您的代码中可能永远不会有这种需求。例如,如果您使用此泛型类型指定函数,则不需要显式指定泛型参数即可使其工作:
function fn<K extends keyof X>(a: A<K>) {
// something
}
fn({ prop: 'a', value: 'a' }); // ok
fn({ prop: 'a', value: 1 }); // not ok, value should be string, because X['a'] is string
fn({ prop: 'b', value: 1 }); // ok
fn({ prop: 'c', value: 1 }); // not ok, value should be boolean, because X['c'] is string
游乐场链接
A的最终目标就是这种类型。(与第一个答案相同(
type A = {
prop: "a";
value: string;
} | {
prop: "b";
value: number;
} | {
prop: "c";
value: boolean;
};
为了制造这种类型,
定义
PropValuePairOfX
type PropValuePairOfX<K extends keyof X> = { prop: K; value: X[K] };
定义
PropValuePairMapOfX
。type PropValuePairMapOfX = { [K in keyof X]: PropValuePairOfX<K> }; // { // a: {prop: 'a', value: string}, // b: {prop: 'b', value: number}, // c: {prop: 'c', value: boolean} // }
最后用
PropValuePairsOfX
提取PropValuePairMapOfX
的值作为UnionType。// This type is the same as A type PropValuePairsOfX = PropValuePairMap[keyof X]; // {prop: 'a', value: string} | {prop: 'b', value: number} | {prop: 'c', value: boolean} // This type is the same as our goal type 😆 const a: PropValuePairsOfX = { prop: 'a', value: 'a' }; // ok const b: PropValuePairsOfX = { prop: 'a', value: 1 }; // not ok, value should be string, because X['a'] is string const c: PropValuePairsOfX = { prop: 'b', value: 1 }; // ok const d: PropValuePairsOfX = { prop: 'c', value: 1 }; // not ok, value should be boolean, because X['c'] is string
(选项(通过使X成为泛型参数类型,我们可以使其成为更通用的类型。
interface X { a: string; b: number; c: boolean; } type PropValuePair<T, K extends keyof T> = { prop: K; value: T[K] }; type PropValuePairMap<T> = { [K in keyof T]: PropValuePair<T, K> }; type PropValuePairsUnion<T> = PropValuePairMap<T>[keyof T]; const validA: PropValuePairsUnion<X> = { prop: 'a', value: 'a' }; // ok const invalidA: PropValuePairsUnion<X> = { prop: 'a', value: 0 }; // ng