主要的问题是,您对NumberKeys<T>
的定义使用了homomorphicmapped type(参见What does "homomorphic mapped type" mean?),其中包括保留属性是否为optional.因此,如果T
甚至有一个可选属性,那么映射的类型将在其属性中包含undefined
,而这undefined
将使其一直进入结果:
type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a" | undefined
undefined
把事情搞砸了,编译器抱怨K
不能索引MyObject
,因为K
可以是undefined
.因此,我们需要做的最大的修复就是压制undefined
.一种方法是使用the mapping modifier -?
使结果中的所有属性都是必需的:
type NumberKeys<T> = {
[K in keyof T]-?: T[K] extends number ? K : never
}[keyof T];
type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a"
这很好,它解决了你的错误:
function process<K extends NumberKeys<MyObject>>(obj: MyObject, key: K): number {
const value = obj[key]; // okay
return value !== undefined ? value * 2 : 0;
}
不幸的是,你仍然没有得到你想要的行为:
type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber"
undefined
走了,但optionalNumber
不在.这是因为映射修改器-?
似乎只从映射结果中移除undefined
,而不从原始属性中移除.这被认为是TypeScrip中的一个错误,就像在microsoft/TypeScript#3optionalNumber
5中报告的那样,但这是一个如此长期存在的错误,我预计它永远不会得到修复.我想,如果他们修复了它,很多现实世界的代码都会被破解.
无论如何,这意味着即使有-?
,optionalNumber
‘S属性仍被视为number | undefined
,并且由于number | undefined
不会扩展number
,因此您的代码中不会 Select 键.解决这个问题的最简单方法是只判断number | undefined
,而不是number
:
type NumberKeys<T> = {
[K in keyof T]-?: T[K] extends number | undefined ? K : never
}[keyof T];
type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber" | "optionalNumber"
很有效.
或者,你可以通过在Required<MyObject>
而不是MyObject
上操作来回避这个问题,使用the Required
utility type(当然,它是通过-?
实现的):
type NKMO = NumberKeys<Required<MyObject>>;
// type NKMO = "mandatoryNumber" | "optionalNumber"
function process<K extends NumberKeys<Required<MyObject>>>(
obj: MyObject, key: K): number {
const value = obj[key]; // okay
return value !== undefined ? value * 2 : 0;
}
这与另一种方法非常相似;两种方法都处理引入undefined
的可选属性,只是方式略有不同.
Playground link to code个