0

在 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[],可以肯定的是,T[P] extends string对于任何P in keyof T.

我在这里做错了什么?


我知道我可以用条件类型解决问题:

export interface Class<T extends string[]> {
  readonly prop: { [P in keyof T]: T[P] extends string ? Generic<T[P]> : never }
}

但我不明白,为什么这应该是必要的。

4

3 回答 3

1

如果你看完整的错误,第三行有一个很大的线索:

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]> }
}

操场

于 2020-11-30T22:11:09.917 回答
0

这是 TypeScript 中的一个已知错误,在这种映射类型的实现中不存在对元组和数组上的映射类型的支持(在 TS3.1添加;请参阅microsoft/TypeScript#27995根据TypeScript 的首席架构师看来:

这里的问题是,当我们为元组或数组实例化通用同态映射类型时,我们只映射到元组和数组类型(参见#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">]......所以我将把它们排除在这个答案之外。

Playground 代码链接

于 2020-12-01T02:32:36.713 回答
0

请注意, for不仅包括数字键,还包括构成 Array 原型的所有方法T extends string[]keyof T证明?这里是:

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 TP in number

export interface Generic<T extends string> {};

export interface Class<T extends string[]> {
    readonly prop: { [P in number]: Generic<T[P]> }
}
于 2020-12-01T16:50:57.937 回答