为何T继承Record<number,X>与Record<string,X>时T[keyof T]表现不同?
该类型行为差异的根本原因
核心是TypeScript对string/number两类索引签名的约束范围规则不同,再叠加泛型约束的最小满足特性,最终导致了校验结果的差异:
- 泛型约束的基础逻辑是
T extends XXX只要求T至少匹配XXX的结构,允许T额外挂载XXX未定义的任意属性。 - 当约束写为
Record<string, {b: boolean}>时,TS对齐了JavaScript运行时“数字索引会隐式转为字符串存储”的行为,强制要求:类型中所有字符串、数字键对应的属性,都必须符合string索引签名的返回类型。此时TS为了类型检查的实用性做了简化:计算T[keyof T]时直接取索引签名对应的{b: boolean}类型,忽略使用场景极少的symbol类型额外属性,因此访问['b']字段可以正常通过校验。 - 当约束换成
Record<number, {b: boolean}>时,number索引签名只会限制T里所有数字键的属性必须是{b: boolean},对T里可能存在的字符串、symbol键的属性类型完全没有约束力。举个最简单的例子:T = {1: {b: true}, username: 'foo'}这个类型完全满足Record<number, {b: boolean}>的约束,但此时T[keyof T]的类型是{b: boolean} | string,联合类型里的string根本不存在b属性,TS自然会抛出ts(2536)错误,提示Type 'b' cannot be used to index type 'T[keyof T]'。
如果要让number约束的版本也通过校验,只需要限定只取数字键对应的属性即可:
interface I<T extends Record<number, {b: boolean}>> { x: T[number & keyof T]['b']; }
内容的提问来源于stack exchange,提问作者Remirror
相关产品推荐
相关产品推荐

