为何TypeScript在映射类型中使用T[P]时,当T为字符串联合类型而非对象却不报错?
这个问题问得特别戳人,我刚摸TypeScript的时候也被这种看似“不合常理”的语法弄晕过——明明T是字符串字面量的联合类型,不是对象,凭啥能在映射类型里用keyof T和T[P]还不报错?
咱们一点点拆解:
首先,你得知道TypeScript里的原始类型(string、number、boolean这些)都有对应的包装对象类型,比如string对应String对象、number对应Number对象。JavaScript运行时里,当你对原始值调用方法(比如'abc'.toString()),会自动把原始值装箱成对应的包装对象,用完再销毁。而TypeScript的类型系统也对齐了这个行为,在类型层面做了兼容处理。
回到你的代码:
type B<T extends 'title' | 'completed'> = { [P in keyof T]: T[P] };
这里的T是'title' | 'completed',属于string的字面量子类型。当你对T使用keyof时,TypeScript不会把它当成单纯的字面量类型,而是自动关联到它的基础类型string对应的包装对象String,所以keyof T其实等价于keyof String——也就是String原型上所有方法、属性的键的联合(比如"toString"、"charAt"、"length"这些)。
而T[P]呢?因为T是string的子类型,TypeScript同样会把它当成String包装对象来做索引访问,所以T[P]就等价于String[P],也就是对应键的类型(比如T['length']就是number,T['toString']就是() => string)。
那为啥TypeScript要这么设计?其实是为了让类型系统更贴合JavaScript的实际运行逻辑,同时也给开发者留了更灵活的类型操作空间。你想想,既然运行时原始值能当成包装对象用,那类型系统里允许对应的类型操作也合情合理对吧?
举个实际的例子,当你实例化B<'title'>时,得到的类型会是一个包含所有String对象属性的复杂类型,每个属性的类型都和String对象上的对应属性一致。
所以你直觉里的“T不是对象所以T[P]无效”,其实是忽略了TypeScript对原始类型包装对象的隐式兼容处理——这可不是bug,是刻意的设计哦😉
备注:内容来源于stack exchange,提问作者Bilal

