在对象上使用
keyof
类型运算符时,我很难理解 TypeScript 的类型。
看看下面的例子:
type TypeA = { [k: number]: boolean };
type AKey = keyof TypeA;
// ^? type AKey = number
type TypeB = { [k: string]: boolean };
type BKey = keyof TypeB;
// ^? type BKey = string | number
TypeScript 文档 包含一条注释,内容如下:
请注意,在此示例中,
是keyof { [k: string]: boolean }
— 这是因为 JavaScript 对象键始终强制转换为字符串,因此 obj[0] 始终与 obj["0"] 相同。string | number
这看起来似乎应该是完全相反的。
AKey
应该是数字(因为它们总是被强制转换为字符串),而 BKey
应该只是一个字符串,因为该类型不允许数字。
如果这还不够令人困惑,那么使用
Record<>
时同样不成立。
这似乎是因为定义使用 in
而不是 :
:
type Record<K extends string | number | symbol, T> = { [P in K]: T; }
type TypeC = { [k in number]: boolean }; // Record<number, boolean>
type CKey = keyof TypeC;
// ^? type CKey = number
type TypeD = { [k in string]: boolean }; // Record<string, boolean>
type DKey = keyof TypeD;
// ^? type DKey = string
所有类型都允许使用数字和字符串作为键,因此类型定义似乎不会以任何方式影响:
const value: TypeA | TypeB | TypeC | TypeD = {
0: false,
"1": true,
};
谁能帮我理解这种类型的马戏团?
keyof
运算符应用于映射类型和具有索引签名的类型时的行为在microsoft/TypeScript#23592的实现拉取请求中指定。规则是:
给定一个对象类型
,X
的解析如下:keyof X
- 如果
包含字符串索引签名,则X
是keyof X
、string
和表示类似符号属性的文字类型的并集,否则number
- 如果
包含数字索引签名,则X
是keyof X
和表示类似字符串和类似符号属性的文字类型的并集,否则number
是表示类似字符串、类似数字和类似符号属性的文字类型的联合。keyof X
TypeScript 将
number
类型的数字键视为 string
类型键的子类型(这是支持数组索引的方便虚构;对象键从来都不是真正的 number
,而是数字 strings。但我离题了,有关更多信息,请参阅具有索引签名的 keyof 类型运算符)。这意味着每个数字键实际上都是字符串键,但并非每个字符串键都是数字键。具有数字索引签名的对象并不声称支持每个字符串键,而具有字符串索引签名的对象确实声称支持每个字符串键,其中也包括数字键。
至于
keyof Record<string, T>
的行为差异是
string
而
keyof {[k: string]: T}
是
string | number
,映射类型的行为在拉取请求中没有改变,因此可以通过这种方式确认它是。至于为什么会这样,那就很难说清楚了。我能找到的最接近规范的答案是 TS 开发团队负责人在 microsoft/TypeScript#31013 中的评论:
至于该行为不适用于映射类型,那么,产生不同的行为就是我们添加不同语法的原因。 如需更深入的研究,请从因此,即使
keyof {[P in K]: T}
是
K
,大概
K
成为
string
也很重要,而它与
keyof {[k: string]: T}
不一致的原因是因为不同的语法允许在两种行为之间进行选择。我猜。无论如何,它指向 microsoft/TypeScript#23592。