T0 的权威答案可以在实现“同态mapped types”或{[K in keyof T]: ...} 形式的映射类型的pull request 中找到,其中T 是泛型类型参数。 (注意,它最初被称为“同构”而不是“同构”,但它是同一回事。)
这种映射类型的正常用例是T 是某个 objdct 类型;在这种情况下,映射会保留来自T 的键名以及它们的“read-onliness”和“optionality”修饰符。 (可以通过在映射类型中显式设置它们来覆盖这些修饰符。)
那么当T 是像string 这样的原始类型时会发生什么?拉取请求说“我们只是产生那个原始类型”。事实证明这非常有用,尤其是在递归使用映射类型的情况下。像
这样的类型
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
将遍历嵌套的对象类型并使每个子属性成为可选的... 并保留原语:
declare const ab: DeepPartial<{ a: { b: string } }>;
const str = ab.a!.b!; // string
这几乎总是人们想要的。如果这样的类型继续向下递归到像length 和toUpperCase 这样的包装原语的所有属性,你最终会得到一个基本上没用的类型。然后获得“正常”版本的唯一方法是使用conditional types 来终止下降而不是映射原语:
type DeepPartial2<T> =
{ [K in keyof T]?: T[K] extends object ? DeepPartial2<T[K]> : T[K] }
这不太方便(在 TS2.8 引入条件类型之前是不可能的)。
在某种意义上,人们通常希望为同态映射的事物保留“相同的一般形状”,但“相同的一般形状”的含义并不总是显而易见的。对象类型保持对象类型。基元保持基元。最初,数组被视为常规对象类型,产生了一种几乎无用的类型,它将数组方法转换为无法识别的东西。这对任何人来说都不愉快,这就是为什么TypeScript 3.1 changed it 映射数组类型仍然是数组类型,而映射元组类型仍然是元组类型。
剩下的就是T1,其中any 的属性的同态映射会产生一个可索引的类型。
之前链接的拉取请求没有进入,但实施者在comment 向GitHub issue 解释了他的推理,将此行为报告为错误:
keyof any 是string | number | symbol,因此应用于any 的映射类型会产生等价于{ [x: string]: XXX }。
注意他说的是“相当于”。 symbol 会自动从映射类型中删除,因此 {[K in "a" | symbol]: string} 会产生 {a: string}。您被允许分别拥有string 和number 索引,因此{[K in string | number]: string} 是{[k: string]: string; [k: number]: string}。但是由于数字键在TypeScript 和JavaScript 中被视为字符串值,因此这与{[k: string]: string} 相同。 (如果您想了解更多关于 string、number 和 symbol 键映射后会发生什么,您可以查看 this documentation。)
就我个人而言,我不是 100% 同意 keyof any 被映射到一个 string 索引而不是 string 和 number,因为 {[K in keyof T]: K} 会显示出不同...但它是一个小问题。
重要的一点来自评论的其余部分:
您可能会争辩说,当同态映射类型应用于any 时,我们应该只产生any,但这会不太精确。例如 { [K in keyof T]: number } 用 T 实例化为 any 真的应该产生 { [x: string]: number } 而不仅仅是 any。
所以你去; any 被视为具有字符串可索引键的对象类型,因此对其进行映射会产生合理的结果。该评论是我所见过的最接近该问题的规范答案的评论。
好的,希望对您有所帮助;祝你好运!
Playground link to code