将泛型与索引类型结合

时间:2019-12-13 20:08:55

标签: typescript

因为这有效:

const f = <T extends string>(x: T) => x;
f("");

interface Dictionary<T> { [key: string]: T; }
const dict: Dictionary<number> = { a: 1 };

我希望以下代码也能正常工作:

interface MyRecord<Key extends string, Value> { [_: Key]: Value };

但是编译器报告_

An index signature parameter type must be 'string' or 'number'.

Key extends string更改为Key extends string | number不会执行任何操作(相同的错误)。

它失败的原因是什么?如何看待正确的解决方案?(最好不使用Any等)

Edit1:

type XY = 'x' | 'y';
const myXY: XY = 'x';
const myString: string = myXY;

因为这有效,所以我假设索引类型具有相同的条件(string的子集可以扮演string的角色,这是索引类型所必需的)。

2 个答案:

答案 0 :(得分:0)

我们来谈谈index signature typesmapped types。它们具有相似的语法,并且执行类似的操作,但是它们并不相同。这是相似之处:

  • 它们都是表示一系列属性的对象类型

  • 语法:在对象类型内,索引签名和映射类型都使用带括号的键状符号,如{[Some Key-like Expression]: T}

现在区别:

索引签名

索引签名描述对象类型或接口的部分,表示具有相同类型的属性 任意数,并带有特定的键密钥类型。当前,该键类型只有两种选择:stringnumber

  • 语法:索引签名的语法如下:

    type StringIndex<T> = {[dummyKeyName: string]: T}
    type NumberIndex<T> = {[dummyKeyName: number]: T} 
    

    有一个虚拟密钥名称(上面的dummyKeyName),它可以是您想要的任何名称,并且在方括号外没有任何意义,其后是{{1 }}或:

  • 对象类型的一部分:索引签名可以与对象类型或接口中的其他属性一起出现:

    string
  • 任意数量的属性:可索引类型的对象不需要为每个可能的键都具有一个属性(除了numberinterface Foo { a: "a", [k: string]: string } 之外,甚至不可能这样做)来自Proxy个对象)。相反,您可以将包含任意数量的此类属性的对象分配给可索引类型。请注意,当您从可索引类型读取属性时,即使启用了string even though this is not strictly type safe,编译器也会假定该属性存在(而不是number)。示例:

    undefined
  • 相同类型的属性:索引签名中的所有属性必须具有相同类型;类型不能是特定键的功能。因此,“属性值与其键相同的对象”不能用索引签名来表示,而不是--strictNullChecks以外的任何符号。如果您想要一个接受type StringDict = { [k: string]: string }; const a: StringDict = {}; // no properties, okay const b: StringDict = { foo: "x", bar: "y", baz: "z" }; // three properties, okay const c: StringDict = { bad: 1, okay: "1" }; // error, number not assignable to boolean const val = a.randomPropName; // string console.log(val.toUpperCase()); // no compiler warning, yet // "TypeError: val is undefined" at runtime 但拒绝{[k: string]: string}的类型,则不能使用索引签名来实现。

  • 仅允许将{a: "a"}{b: "c"}作为密钥类型:当前,您可以使用string索引签名来表示类似字典的类型,或者使用{{1 }}索引签名,代表类似数组的类型。而已。您无法将类型扩展为number或将其范围缩小为一组特定的stringnumber文字,例如string | numberstring。 (您关于为什么应该接受更窄的集合的推理是合理的,但这不是它的工作原理。规则是“索引签名参数类型必须 number"a"|"b"” 。)relax this restriction and allow arbitrary key types in an index signature, see microsoft/TypeScript#26797正在进行一些工作,但似乎至少目前stalled

映射类型

另一方面,映射类型表示整个对象类型,而不是接口,表示可能具有不同类型的属性特定集 >,带有特定键类型的键。 您可以为此使用任何键类型,尽管最常见的是文字的并集(如果使用1|2string,则映射类型的那一部分变成了。 ..猜猜是什么?一个索引签名!)接下来,我将仅使用文字的并集作为键集。

  • 语法:映射类型的语法如下:

    number

    引入了一个新的类型变量string,该变量遍历键集number的键联合的每个成员。新类型变量即使在括号内也仍然在属性值type Mapped<K extends keyof any> = {[P in K]: SomeTypeFunction<P>}; type SomeTypeFunction<P extends keyof any> = [P]; // whatever 中。

  • 整个对象类型:映射类型是整个对象类型。它不能与其他属性一起出现,也不能出现在界面中。就像是联合或相交类型:

    P
  • 一组特定的属性:与索引签名不同,映射类型必须在键集中每个键仅具有一个属性。 (这是一个例外,如果该属性恰巧是可选的,要么是因为它是从具有可选属性的类型映射的,要么是因为您使用in修饰符将该属性修改为可选的):

    K
  • 属性类型可能会有所不同:由于迭代键类型参数在属性类型的范围内,因此您可以根据键的功能来更改属性类型,如下所示:

    SomeTypeFunction<P>
  • 可以使用任何密钥集:您不限于interface Nope { [K in "x"]: K; // errors, can't appear in interface } type AlsoNope = { a: string, [K in "x"]: K; // errors, can't appear alongside other properties } ?。您可以使用任何一组type StringMap = { [K in "foo" | "bar" | "baz"]: string }; const d: StringMap = { foo: "x", bar: "y", baz: "z" }; // okay const e: StringMap = { foo: "x" }; // error, missing props const f: StringMap = { foo: "x", bar: "y", baz: "z", qux: "w" }; // error, excess props 文字或type SameName = { [K in "foo" | "bar" | "baz"]: K }; /* type SameName = { foo: "foo"; bar: "bar"; baz: "baz"; } */ 文字,甚至可以使用string值(但是这些值使用起来更加复杂,因此我忽略了它)。您也可以在其中使用numberstring,但是当发生这种情况时,您会立即获得索引签名:

    number

    由于可以使用任何密钥集,因此可以是通用的:

    symbol

这就是您制作string的方式。它不能是可索引的类型。仅映射类型。请注意,the built-in Record<K, T> utility type本质上是相同的(它允许number),因此您可能想使用它而不是自己的。

好的,希望能有所帮助;祝你好运!

Link to code

答案 1 :(得分:0)

您可以使用打字稿的 Record<Key, Value> 实用程序代替索引签名。

interface MyObject<K extends string = string> {
  someProperty: Record<K, any>;
}