TypeScript - 受限元组泛型的类型保存映射类型
TypeScript - typesave mapping types of restricted tuple generic
在 TypeScript 中,这不是编译:
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> }
}
特别是 Generic<T[P]>
失败并显示 Type 'T[P]' does not satisfy the constraint 'string'.
。然而,由于T extends string[]
,可以肯定的是,对于任何P in keyof T
.
,T[P] extends string
我做错了什么?
我知道我可以用条件类型解决问题:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: T[P] extends string ? Generic<T[P]> : never }
}
但我不明白为什么这是必要的。
如果你看完整的错误,第三行有一个大线索:
Type 'T[P]' does not satisfy the constraint 'string'.
Type 'T[keyof T]' is not assignable to type 'string'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
Type 'T[string]' is not assignable to type 'string'.(2344)
问题是 keyof
任何数组类型(或元组类型)都会更像 string | number | symbol
。一个数组在这些键上也有比它更多的成员类型。例如:
// (...items: string[]) => number
type PushFunction = string[]['push']
查看此代码段。数组键中不仅仅是数字:
// number | "0" | "1" | "2" | "length" | "toString"
// | "toLocaleString" | "pop" | "push" | "concat"
// | "join" | "reverse" | "shift" | "slice" | "sort"
// | "splice" | "unshift" | "indexOf"
// | ... 15 more ... | "includes"
type ArrayKeys = keyof [1,2,3]
而Generic<T>
要求T
是一个字符串,但是,如图所示,并非数组所有键的所有值都是字符串。
您可以通过将数组键与 number
相交来非常简单地修复映射类型,通知打字稿您只关心数字键(这是数组索引):
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T & number]: Generic<T[P]> }
}
这是 TypeScript 中的一个已知错误,其中支持(在 TS3.1 中添加)mapped types over tuples and arrays does not exist inside the implementation of such mapped types; see microsoft/TypeScript#27995. It seems that according to TypeScript 的首席架构师:
The issue here is that we only map to tuple and array types when we instantiate a generic homomorphic mapped type for a tuple or array (see #26063).
所以从外部,当你在数组或元组上使用映射类型时,映射会保留数组-或-输入的元组性,仅映射到数字属性:
declare const foo: Class<["a", "b", "c"]>;
// (property) prop: [Generic<"a">, Generic<"b">, Generic<"c">]
const zero = foo.prop[0]; // Generic<"a">;
const one = foo.prop[1]; // Generic<"b">;
但在内部,编译器仍然将P in keyof T
视为遍历T
的每个键,包括任何可能的非数字。
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> } // error!
}
如您所见,有解决此问题的方法,microsoft/TypeScript#27995 中提到了这些解决方法。我认为最好的是与你的条件类型基本相同:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<Extract<T[P], string>> }
}
其中的其他类型要么不适用于像 T
这样的泛型类型,要么生成不再是真正的数组或元组的映射类型(例如,{0: Generic<"a">, 1: Generic<"b">, 2: Generic<"c">}
而不是 [Generic<"a">, Generic<"b">, Generic<"c">]
...所以我将把它们排除在这个答案之外。
请注意,对于 T extends string[]
,keyof T
不仅包括数字键,还包括构成 Array 原型的所有方法。
证明?这是:
type StringArrayKeys = keyof string[];
// Produces:
// type StringArrayKeys = number | "length" | "toString" | "toLocaleString" | "pop" |
// "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" |
// "unshift" | "indexOf" | "lastIndexOf" | ... 16 more
因此,对于您的示例,最简单的修复方法是将 P in keyof T
替换为 P in number
:
export interface Generic<T extends string> {};
export interface Class<T extends string[]> {
readonly prop: { [P in number]: Generic<T[P]> }
}
在 TypeScript 中,这不是编译:
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> }
}
特别是 Generic<T[P]>
失败并显示 Type 'T[P]' does not satisfy the constraint 'string'.
。然而,由于T extends string[]
,可以肯定的是,对于任何P in keyof T
.
T[P] extends string
我做错了什么?
我知道我可以用条件类型解决问题:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: T[P] extends string ? Generic<T[P]> : never }
}
但我不明白为什么这是必要的。
如果你看完整的错误,第三行有一个大线索:
Type 'T[P]' does not satisfy the constraint 'string'.
Type 'T[keyof T]' is not assignable to type 'string'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string'.
Type 'T[string]' is not assignable to type 'string'.(2344)
问题是 keyof
任何数组类型(或元组类型)都会更像 string | number | symbol
。一个数组在这些键上也有比它更多的成员类型。例如:
// (...items: string[]) => number
type PushFunction = string[]['push']
查看此代码段。数组键中不仅仅是数字:
// number | "0" | "1" | "2" | "length" | "toString"
// | "toLocaleString" | "pop" | "push" | "concat"
// | "join" | "reverse" | "shift" | "slice" | "sort"
// | "splice" | "unshift" | "indexOf"
// | ... 15 more ... | "includes"
type ArrayKeys = keyof [1,2,3]
而Generic<T>
要求T
是一个字符串,但是,如图所示,并非数组所有键的所有值都是字符串。
您可以通过将数组键与 number
相交来非常简单地修复映射类型,通知打字稿您只关心数字键(这是数组索引):
export interface Generic<T extends string> {
}
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T & number]: Generic<T[P]> }
}
这是 TypeScript 中的一个已知错误,其中支持(在 TS3.1 中添加)mapped types over tuples and arrays does not exist inside the implementation of such mapped types; see microsoft/TypeScript#27995. It seems that according to TypeScript 的首席架构师:
The issue here is that we only map to tuple and array types when we instantiate a generic homomorphic mapped type for a tuple or array (see #26063).
所以从外部,当你在数组或元组上使用映射类型时,映射会保留数组-或-输入的元组性,仅映射到数字属性:
declare const foo: Class<["a", "b", "c"]>;
// (property) prop: [Generic<"a">, Generic<"b">, Generic<"c">]
const zero = foo.prop[0]; // Generic<"a">;
const one = foo.prop[1]; // Generic<"b">;
但在内部,编译器仍然将P in keyof T
视为遍历T
的每个键,包括任何可能的非数字。
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<T[P]> } // error!
}
如您所见,有解决此问题的方法,microsoft/TypeScript#27995 中提到了这些解决方法。我认为最好的是与你的条件类型基本相同:
export interface Class<T extends string[]> {
readonly prop: { [P in keyof T]: Generic<Extract<T[P], string>> }
}
其中的其他类型要么不适用于像 T
这样的泛型类型,要么生成不再是真正的数组或元组的映射类型(例如,{0: Generic<"a">, 1: Generic<"b">, 2: Generic<"c">}
而不是 [Generic<"a">, Generic<"b">, Generic<"c">]
...所以我将把它们排除在这个答案之外。
请注意,对于 T extends string[]
,keyof T
不仅包括数字键,还包括构成 Array 原型的所有方法。
证明?这是:
type StringArrayKeys = keyof string[];
// Produces:
// type StringArrayKeys = number | "length" | "toString" | "toLocaleString" | "pop" |
// "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" |
// "unshift" | "indexOf" | "lastIndexOf" | ... 16 more
因此,对于您的示例,最简单的修复方法是将 P in keyof T
替换为 P in number
:
export interface Generic<T extends string> {};
export interface Class<T extends string[]> {
readonly prop: { [P in number]: Generic<T[P]> }
}