TypeScript为何无法推断泛型T的T[keyof T]类型?
问题根源
原写法无法通过类型检查的核心原因是:泛型参数T extends ABC仅约束T是联合类型ABC的子类型,编译器不会在类内部自动展开T[keyof T]的所有可能取值。在泛型T未被实际指定的类内部上下文里,T[keyof T]是一个独立的泛型关联类型,和ABC[keyof ABC](即string | number | Date)没有被编译器认可的直接父-子类型关系,因此直接单断言会失败,必须绕开类型检查走双重断言。
无双重断言的兼容写法(保留对象泛型参数)
如果需要保留「实例化时指定对象类型」的泛型设计,只需要提前提取ABC联合类型的所有合法属性值类型,由于T必然是ABC的子类型,T的任意属性值都属于该值类型的子集,这一断言是完全类型安全的,编译器不会报错:
interface AB { a: string b: number } interface BC { b: number c: Date } type ABC = AB | BC // 提前提取ABC所有可能的属性值类型 type ABCPropValue = ABC[keyof ABC]; // string | number | Date class Getter<T extends ABC> { key: keyof T; constructor(key: keyof T) { this.key = key; } get(thing: T): string { const value = thing[this.key] as ABCPropValue; return value.toString(); } } const getter = new Getter<AB>('b'); // 类型校验正常 getter.get({ a: "test", b: 1 });
以key字面量为类型参数的实现
完全可以将key的字面量类型作为核心泛型参数,这种写法不需要手动传入泛型,实例化时TypeScript会自动根据传入的key推断类型,同时自动约束get方法的入参必须是包含对应key的合法ABC子类型,全程不需要任何类型断言,类型安全性更高:
interface AB { a: string b: number } interface BC { b: number c: Date } type ABC = AB | BC type ABCKeys = keyof ABC; // K为key的字面量类型,约束为ABC的合法键名 class Getter<K extends ABCKeys> { key: K; constructor(key: K) { this.key = key; } // 自动约束入参:必须是ABC的子类型,且包含当前key get<T extends ABC & Record<K, unknown>>(thing: T): string { const value = thing[this.key]; return value.toString(); } } // 实例化时自动推断泛型,不需要手动指定 const getterForB = new Getter('b'); getterForB.get({ a: "x", b: 123 }); // 合法,AB类型包含b getterForB.get({ b: 456, c: new Date() }); // 合法,BC类型包含b // getterForB.get({ a: "x", c: new Date() }); // 报错,入参不包含b属性 const getterForA = new Getter('a'); getterForA.get({ a: "test", b: 789 }); // 合法 // getterForA.get({ b: 789, c: new Date() }); // 报错,BC类型不包含a属性
这种实现的优势:
- 不需要手动传入泛型参数,构造体验更简洁
- 入参校验逻辑自动收敛到key的匹配关系上,符合「传入key,访问任意包含该key的合法对象对应属性」的原始需求
- 无任何强制类型断言,所有类型检查由编译器完成,不存在类型安全隐患
内容的提问来源于stack exchange,提问作者Joel Newman
相关产品推荐
相关产品推荐

