创建一个 TypeScript Record,其中第二个类型参数取决于第一个类型参数?
我正在尝试在 TS 中定义一个类型,用于表示将字符串(我们称之为 key)映射到一个函数的对象,其中一个参数始终是 key。
我目前拥有的是一个记录,其中 K 是一个 string,值是一个函数,如下所示:
const IsCountry: Record<string, (name: string) => boolean> = {
"US": (name) => true,
"Caracas": => (name) => false
}
这很好用,但我想看看是否可以进一步缩小范围,通过让 TypeScript 推断函数中的 name 应该仅是 key 字符串,这样如果我尝试传入其他任何东西,类型检查器就会发出警告或错误:
IsCountry["US"]("Caracas"); //SHOULD error out, but it doesn't
在当前设计下,该调用不会出错,因为函数的参数可以接受任意string。
谢谢你!
答案#1
可以使用映射类型来实现。但我们需要有一个类型来表示可能的键。下面我创建了字符串类型的联合 - Countries,并且类型被缩小,所以函数只能使用确切的键来调用。
type CountryRecord<A extends string> = {
[Key in A]: (x:Key) => boolean // mapped key - function argument needs to match the key
}
// type which defines all countries keys
type Countries = "US" | "Caracas";
const IsCountry: CountryRecord<Countries> = {
"US": (name) => true,
"Caracas": (name) => false
}
IsCountry["US"]("Caracas") // error
IsCountry["US"]("US") // correct
另外,如果定义静态列表是一个问题,那么我们可以从现有结构创建一个类型,也许你有一些对象,其中包含这些国家的键。下面是这样一个结构的示例,它可以作为我们类型的蓝图:
const Countries = {
"US": "United States of America",
"Caracas": "Caracas"
}
type Countries = keyof typeof Countries; // type created from existing structure
// now the same usage:
const IsCountry: CountryRecord<Countries> = {
"US": (name) => true,
"Caracas": (name) => false
}
编辑
正如 @jcalz 在评论中正确提到的,通过恒等函数可以实现这种效果,它将正确应用类型。考虑如下:
type CountryRecord<T> = { [K in keyof T]: (name: K) => boolean };
const asCountryRecord = <T extends CountryRecord<T>>(t: T) => t; // identity function which applies the wanted type by inferencing the given structure
const IsCountry = asCountryRecord({
"US": (name) => true,
"Caracas": (name) => false
});
让我们解释一下这个恒等函数是如何工作的:
const asCountryRecord = <T extends CountryRecord<T>>(t: T) => t;
<T extends CountryRecord<T>>- T 是一个可分配给 CountryRecord 的类型,因此它实际上需要具有期望的结构伪代码 -{key: key=>bool}(t: T)- 参数是具有类型 T 的事物,所以它的意思是 TS 将对这里提供的结构进行推断,并且 T 将等于参数中值的类型。=> t- 这意味着函数什么也不做,只返回相同的数据,这就是为什么我说它在运行时只是一个恒等函数。
换句话说,asCountryRecord 是一个工具,它使我们能够使用参数的推断来完成泛型类型参数。解决方案与之前的命题相同,但类型不是静态存在的类型,而是从参数中推断出来的。
