typescript - 创建一个 TypeScript 记录,其中第二个类型参数取决于第一个?
问题描述
我试图在 TS 中定义一个对象的类型,该对象将字符串(让我们称之为key
)映射到一个函数,其中一个参数始终是key
.
我目前拥有的是一个记录,它有一个string
for K 和一个函数的值,如下所示:
const IsCountry: Record<string, (name: string) => boolean> = {
"US": (name) => true,
"Caracas": => (name) => false
}
哪个工作正常,但我想看看我是否可以通过让 TS 弄清楚name
函数中的 应该只是它们key
的字符串来进一步缩小范围,这样如果我尝试传递其他任何东西,类型检查器就会抛出警告或错误:
IsCountry["US"]("Caracas"); //SHOULD error out, but it doesn't
在当前设计下,该调用不会出错,因为函数的参数可以采用任何string
.
谢谢!
解决方案
可以通过使用映射类型来实现。但是我们需要一个类型来表示可能的键。下面我创建了字符串类型的联合 -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 将等于参数中的 typeof 值。=> t
- 这意味着该函数什么都不做,只返回相同的数据,这就是为什么我说它只是运行时的标识。
换句话说asCountryRecord
,它是一种工具,可以使用参数的推断来实现泛型类型参数。解决方案与先前的命题相同,但不是静态现有类型,而是从参数推断类型。
推荐阅读
- vue.js - Vuejs + vue-html2pdf:未定义窗口
- environment-variables - 使用 repo 机密在 gh 操作中创建 .env 文件
- sql - Postgresql 触发器更新整个列而不是特定行
- ssl - 在具有 TLS 的服务器中仅启用 secp384r1 时,0-RTT 不起作用。为什么?
- vue.js - 尝试不显示 v-model 更改,直到我想显示它
- excel - 如何从excel中两个日期之间的日期列表中省略周末日期
- c++ - 将 2 个 32 位整数相乘而不会溢出
- java - 无法为 Android 应用程序初始化应用程序检查
- amazon-web-services - AWS Lambda/Secrets Manager 权限不再起作用 (Pulumi)
- swift - 我正在尝试从 Swift 5 输入格式化的日期字符串为“yyyy-mm-dd H:i:s +0000”mySQL。相反,我得到“yyyy-mm-dd H:i:s 0000”。如何添加“+”?