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
和类似的。)
编辑1:
type XY = 'x' | 'y';
const myXY: XY = 'x';
const myString: string = myXY;
由于这有效,我假设与索引类型相同(string
可以构成string
索引类型所需角色的子集)。
解决方案
让我们谈谈索引签名类型和映射类型。它们具有相似的语法并做相似的事情,但它们并不相同。以下是相似之处:
它们都是代表一系列属性的对象类型
语法:索引签名和映射类型都在对象类型中使用带括号的 keylike 表示法,如
{[Some Key-like Expression]: T}
现在来看看差异:
索引签名
索引签名描述对象类型或接口的一部分,表示任意数量的相同类型的属性,具有来自特定键类型的键。目前,此密钥类型只有两种选择:string
或number
。
语法:索引签名的语法如下所示:
type StringIndex<T> = {[dummyKeyName: string]: T} type NumberIndex<T> = {[dummyKeyName: number]: T}
有一个虚拟键名(
dummyKeyName
上面),可以是任何你想要的,并且在括号之外没有任何意义,然后是类型注释(:
)string
或number
。对象类型的一部分:索引签名可以与对象类型或接口中的其他属性一起出现:
interface Foo { a: "a", [k: string]: string }
任意数量的属性:可索引类型的对象不需要对每个可能的键都有一个属性(这甚至不可能为对象
string
或number
除了Proxy
对象之外)。相反,您可以将包含任意数量的此类属性的对象分配给可索引类型。请注意,当您从可索引类型读取属性时,编译器将假定该属性存在(与 相对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
或number
作为键类型:目前您可以使用string
索引签名来表示类字典类型,或使用number
索引签名来表示类数组类型。而已。您不能将类型扩大到string | number
或缩小到特定的一组string
或number
文字,如"a"|"b"
or1|2
。(你关于为什么它应该接受更窄集合的推理是合理的,但这不是它的工作原理。规则是“索引签名参数类型必须是string
或number
”。)正在做一些工作来放松这个限制并允许任意键类型在索引签名中,请参阅 microsoft/TypeScript#26797 ,但至少目前似乎停滞不前。
映射类型
另一方面,映射类型描述了整个对象类型,而不是接口, 表示可能变化类型的特定属性集,具有来自某个键类型的键。 您可以为此使用任何键类型,尽管文字的联合是最常见的(如果您使用or ,那么映射类型的那部分会变成......猜猜是什么?一个索引签名!)在接下来的内容中,我将只使用文字的联合作为键集。string
number
语法:映射类型的语法如下所示:
type Mapped<K extends keyof any> = {[P in K]: SomeTypeFunction<P>}; type SomeTypeFunction<P extends keyof any> = [P]; // whatever
引入了一个新的类型变量
P
,它遍历in
键集的键联合的每个成员K
。新类型变量仍在属性 value 的范围内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
或number
。您可以使用任何一组string
文字或number
文字,甚至是symbol
值(但使用起来更加复杂,所以我忽略了这一点)。您也可以在其中使用string
ornumber
,但是当发生这种情况时,您会立即获得索引签名: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
并且由于可以使用任何键集,它可以是通用的:
type MyRecord<Key extends string, Value> = { [P in Key]: Value };
这就是你将如何制作MyRecord
. 它不能是可索引的类型;只有一个映射类型。请注意,内置Record<K, T>
实用程序类型本质上是相同的(它允许K extends string | number | symbol
),因此您可能希望使用它而不是您自己的。
好的,希望有帮助;祝你好运!
推荐阅读
- python - 使用 plotly express 绘制多个极线图
- android - 动态更改选项卡布局的未选择选项卡颜色
- kubernetes - 可以引用当前 Namespace 的 ConfigMap
- java - 一个 Gremlin 查询以获取多个顶点的所有连接顶点
- discord.js - 更新所有用户的背景变量
- python - Tkinter 应用程序如何在 2 个监视器上运行?
- security - Elasticsearch 查询清理和安全性
- amazon-web-services - 当值具有特殊字符时,ElasticSearch 通配符不返回
- azure-active-directory - 程序化 Azure AD 应用程序发现
- recursion - 如何编写一个计算列表中增加的函数?