将泛型与索引类型相结合 [英] Combining generics with index type

查看:32
本文介绍了将泛型与索引类型相结合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

既然这样有效:

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

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

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

I was expecting following code to work as well:

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 什么都不做(同样的错误).

Changing Key extends string to Key extends string | number does nothing (same error).

它失败的原因是什么?如何看起来是正确的解决方案?(最好不使用 Any 和类似的.)

What is the reason why it fails and how would look a correct solution? (Preferably without using Any and similar.)

编辑 1:

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

由于这有效,我假设索引类型也同样成立(string 的子集可以充当索引类型所需的 string 的角色).

Since this works, I was assuming same holds with indexed types (subset of string can pose in a role of string which is required by indexed type).

推荐答案

来说说索引签名类型映射类型.它们具有相似的语法并做类似的事情,但它们并不相同.以下是相似之处:

Let's talk about index signature types and mapped types. They have similar syntax and do similar-ish things, but they're not the same. Here are the similarities:

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

  • They are both object types representing a range of properties

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

Syntax: both index signatures and mapped types use bracketed keylike notation within an object type, as in {[Some Key-like Expression]: T}

现在的差异:

索引签名描述对象类型或接口的一部分,表示任意数量相同类型的属性,带有来自某个特定对象的键键类型.目前,此密钥类型只有两种选择:stringnumber.

Index signatures describe part of an object type or interface representing an arbitrary number of properties of the same type, with keys from a certain key type. Currently, there are only two choices for this key type: string, or number.

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

  • Syntax: The syntax for an index signature looks like this:

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

有一个虚拟键名(上面的dummyKeyName)可以是任何你想要的,括号外没有任何意义,后面跟着一个类型注释(:) 的 stringnumber.

There is a dummy key name (dummyKeyName above) which can be whatever you want and does not have any meaning outside the brackets, followed by a type annotation (:) of either string or number.

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

Part of an object type: an index signature can appear alongside other properties in an object type or interface:

interface Foo {
  a: "a",
  [k: string]: string
}

  • 任意数量的属性:一个可索引类型的对象不需要为每个可能的键都拥有一个属性(这对于 string 甚至是不可能做到的)编号 除了Proxy 对象).相反,您可以将包含任意数量的此类属性的对象分配给可索引类型.请注意,当您从可索引类型读取属性时,编译器将假定该属性存在(而不是 undefined),即使启用了 --strictNullChecks即使这不是严格的类型安全.示例:

  • Arbitrary number of properties: an object of an indexable type is not required to have a property for every possible key (which is not even really possible to do for string or number aside from Proxy objects). Instead, you can assign an object containing an arbitrary number of such properties to an indexable type. Note that when you read a property from an indexable type, the compiler will assume the property is present (as opposed to undefined), even with --strictNullChecks enabled, even though this is not strictly type safe. Example:

    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"} 的类型,你不能用索引签名来做到这一点.

  • Properties of the same type: all of the properties in an index signature must be of the same type; the type cannot be a function of the specific key. So "an object whose property values are the same as their keys" cannot be represented with an index signature as anything more specific than {[k: string]: string}. If you want a type that accepts {a: "a"} but rejects {b: "c"}, you can't do that with an index signature.

    仅允许 stringnumber 作为键类型:目前您可以使用 string 索引签名来表示字典-like 类型,或 number 索引签名来表示类似数组的类型.就是这样.您不能将类型扩展为 string |number 或将其缩小为一组特定的 stringnumber 文字,例如 "a"|"b"1|2.(关于为什么它应该接受更窄的集合的推理是合理的,但这不是它的工作原理.规则是索引签名参数类型必须 be stringnumber".)有一些工作正在放宽此限制并允许任意键类型在索引签名中,请参阅 microsoft/TypeScript#26797,但似乎是 停滞至少现在是这样.

    Only string or number is allowed as the key type: currently you can use a string index signature to represent a dictionary-like type, or a number index signature to represent an array-like type. That's it. You can't widen the type to string | number or narrow it to a particular set of string or number literals like "a"|"b" or 1|2. (Your reasoning about why it should accept a narrower set is plausible but that's not how it works. The rule is "an index signature parameter type must be string or number".) There was some work being done to relax this restriction and allow arbitrary key types in an index signature, see microsoft/TypeScript#26797, but seems to be stalled at least for now.

    另一方面,映射类型描述了一个完整的对象类型,而不是一个接口,代表了一个特定的属性集可能变化的类型,使用来自特定密钥类型的密钥.您可以为此使用任何键类型,尽管最常见的是文字联合(如果您使用 stringnumber,则该部分的映射类型变成了...猜猜怎么着?索引签名!)接下来我将只使用文字的联合作为键集.

    A mapped type on the other hand describes an entire object type, not an interface, representing a particular set of properties of possibly varying types, with keys from a certain key type. You can use any key type for this, although a union of literals is most common (if you use string or number, then that part of the mapped type turns into... guess what? an index signature!) In what follows I will use only a union of literals as the key set.

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

    • Syntax: The syntax for a mapped type looks like this:

    type Mapped<K extends keyof any> = {[P in K]: SomeTypeFunction<P>};
    type SomeTypeFunction<P extends keyof any> = [P]; // whatever
    

    引入了一个新的类型变量P,它遍历键集Kin 键联合的每个成员.新类型变量仍然在属性值 SomeTypeFunction

    的范围内,即使它在括号之外.

    A new type variable P is introduced, which iterates over each member of the union of keys in the key set K. The new type variable is still in scope in the property value SomeTypeFunction<P>, even though it's outside the brackets.

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

    An entire object type: a mapped type is the entire object type. It cannot appear alongside other properties and cannot appear in an interface. It's like a union or intersection type in that way:

    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
    }
    

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

  • A particular set of properties: unlike index signatures, a mapped type must have exactly one property per key in the key set. (An exception to this is if the property happens to be optional, either because it's mapped from a type with optional properties, or because you modify the property to be optional with the ? modifier):

    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
    

  • 属性类型可能会有所不同:因为迭代键类型参数在属性类型的范围内,您可以将属性类型作为键的函数进行更改,如下所示:

  • Property types may vary: because the iterating key type parameter is in scope in the property type, you can vary the property type as a function of the key, like this:

    type SameName = { [K in "foo" | "bar" | "baz"]: K };
    /* type SameName = {
        foo: "foo";
        bar: "bar";
        baz: "baz";
    } */
    

  • 可以使用任何键集:您不限于stringnumber.您可以使用任何一组 string 文字或 number 文字,甚至 symbol 值(但这些值使用起来更加复杂,所以我无视).您也可以在其中使用 stringnumber ,但在发生这种情况时您会立即获得索引签名:

  • Any key set may be used: you are not restricted to string or number. You can use any set of string literals or number literals, and even symbol values (but those are even more complicated to use so I'm ignoring that). You can also use string or number in there, but you immediately get an index signature when that happens:

    type AlsoSameName = { [K in "a" | 1]: K };    
    /* type AlsoSameName = {
        a: "a";
        1: 1;   
    } */
    const x: AlsoSameName = { "1": 1, a: "a" }
    
    type BackToIndex = { [K in string]: K }
    /* type BackToIndex = {
        [x: string]: string;
    }*/
    const y: BackToIndex = { a: "b" }; // see, widened to string -> string
    

    而且由于可以使用任何键集,因此它可以是通用的:

    And since any key set may be used, it can be generic:

    type MyRecord<Key extends string, Value> = { [P in Key]: Value };
    

  • 这就是您制作 MyRecord 的方式.它不能是可索引的类型;只有一个映射类型.并注意内置Record 实用程序类型 本质上是相同的(它允许 K 扩展字符串 | 数字 | 符号),因此您可能希望使用它而不是您自己的.

    So that's how you would make MyRecord. It can't be an indexable type; only a mapped type. And note that the built-in Record<K, T> utility type is essentially the same (it allows K extends string | number | symbol), so you might want to use that instead of your own.

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

    Okay, hope that helps; good luck!

    链接到代码

    这篇关于将泛型与索引类型相结合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆