假设我有两个不同的函数,我想根据传递的函数名调用它们,同时,我想为函数参数添加类型注解。
最小示例:
const a = (options: { o1: number }) => {}
const b = (options: { o2: string }) => {}
const functions = { a, b }
type A = {
name: 'a'
options: Parameters<typeof a>[0]
}
type B = {
name: 'b'
options: Parameters<typeof b>[0]
}
type Data = A | B
(data: Data) => {
functions[data.name](data.options)
}
但是TypeScript不允许传递data.options
:
TS2345: Argument of type '{ o1: number; } | { o2: string; }' is not assignable to parameter of type '{ o1: number; } & { o2: string; }'. Type '{ o1: number; }' is not assignable to type '{ o1: number; } & { o2: string; }'. Property 'o2' is missing in type '{ o1: number; }' but required in type '{ o2: string; }'.
看起来像TypeScript转换
type Data = A | B
进入
type Data = {
name: 'a' | 'b'
options: Parameters<typeof a>[0] | Parameters<typeof b>[0]
}
这对我来说似乎是不正确的
1条答案
按热度按时间v8wbuo2f1#
这里的问题我称之为“相关联合类型”,这在microsoft/TypeScript#30581中报告过,并通过microsoft/TypeScript#47109得到了解决,microsoft/TypeScript#47109建议进行某种重构,以帮助编译器理解您正在做什么。
通常,如果有一个函数
f
和一个参数x
,两者都是联合类型,如下所示:则使用该参数调用该函数是不安全的,TypeScript编译器将生成一个错误,告诉您:
该错误消息可能会令人困惑,但它只是说明参数联合不起作用;它所知道的唯一可行的方法是参数交集,这是根据TS3.3中引入并在microsoft/TypeScript#29011中实现的对调用联合类型的支持。
这将是一个错误,这是有道理的,因为就编译器所知,
f
可能是(options: { o1: number }) => void)
类型,而x
是{ o2: string }
类型。也就是说,它们可能不匹配。现在,在您的程式码中,您要执行下列动作:
编译器会产生与上述相同的错误,原因也相同:
f
和x
都是联合类型,就编译器所知,它们可能不匹配。但是编译器没有意识到f
和x
的类型是相互关联的。它们都依赖于data
的类型,这样就不可能出现不匹配。这实际上是TypeScript的一个限制。如果您保留联合类型的
f
和x
,编译器将无法看到它们之间的任何关联。microsoft/TypeScript#47109中推荐的重构方法是使函数成为generic而不是联合,并向其传递相应泛型类型的参数。为了使其正常工作,必须非常小心地管理函数和参数类型。
首先,我们可以创建一个
DataMap
Map接口,它表示一个基本的键-值关系,我们可以从这个关系构建其他类型:然后,我们可以将
functions
变量注解为mapped type,它显式迭代DataMap
的键和值类型:Data
不是硬编码的A
和B
的直接并集,我们可以将Data
重写为通用的 * 分布式对象类型 *(如ms/TS#47109中所述,它是我们直接将index:根据需要,我们可以定义
A
或B
:如果需要,可以使用不带类型参数的
Data
类型(由于它的泛型默认值),并查看它是否与您的版本是相同的类型:K
,我们接受Data<K>
,而不是接受Data
:是的,没有编译器错误!这是因为编译器将
functions[data.name]
和data.options
类型视为相应的相关泛型:你可以看到参数
x
的类型是DataMap[K]
,函数f
的类型是(options: DataMap[K]) => void
,所以这个参数就是函数接受的类型,编译器不再出错。Playground代码链接