TypeScript报错‘Source provides no match for variadic element at position 0 in target.(2322)’原因及代码类型赋值问题咨询
我来帮你捋清楚这个问题哈!先看看你的代码场景:
你定义了一套协议相关的类型,然后在registerInProtocol函数里,想把传入的listener(类型是ProtocolListener<D[K]>)赋值给一个类型更宽泛的implementation(ProtocolListener<ProtocolBlueprint>),但TypeScript抛出了类型不兼容的错误,你困惑的点在于D[K]明明就是ProtocolBlueprint的子类型,为啥赋值不成立?
先把你的代码贴出来方便对照:
type Data = { [key: string]: Data } | string | number | boolean; type ProtocolBlueprint = { args: Data[], result: Data }; type ProtocolDesc = { [key: string] : ProtocolBlueprint }; type MessageSender = number; type AllowBeAsync<T> = Promise<T> | T; type ProtocolListener<B extends ProtocolBlueprint> = (...args: [...B["args"], sender: MessageSender]) => AllowBeAsync<B["result"]>; function registerInProtocol<D extends ProtocolDesc, K extends keyof D>(name: K, listener: ProtocolListener<D[K]>): void { // ... // I CAN'T CHANGE `implementation` TYPE FROM ProtocolListener<ProtocolBlueprint>` TO `ProtocolListener<D[K]>` IS MUST STAY MORE BROAD ABSTRACT TYPE. const implementation: ProtocolListener<ProtocolBlueprint> = listener; // why this assignment is not valid? template paramter `D[K]` evaluates to `ProtocolBlueprint`. console.log(implementation); // ... } type Supported = { "countUsers": { args: [], result: number }; "doesUserExist": { args: [id: number], result: boolean }; } registerInProtocol<Supported, "countUsers">("countUsers", () => { return 23 });
对应的报错信息是:
Type 'ProtocolListener<D[K]>' is not assignable to type 'ProtocolListener
'.
Types of parameters 'args' and 'args' are incompatible.
Type '[...Data[], sender: number]' is not assignable to type '[...D[K]["args"], sender: number]'.
Source provides no match for variadic element at position 0 in target.(2322)
核心原因:函数类型的逆变特性
这里的关键在于函数类型的参数是逆变的,简单说:
- 如果有一个宽泛的函数类型(比如
(arg: Animal) => void),你不能把一个更具体的函数(比如(arg: Dog) => void)赋值给它——因为宽泛函数可以接受任何Animal,但具体函数只能处理Dog,把它当宽泛函数用的话,万一传入Cat就会出问题。 - 反过来,把宽泛函数赋值给具体函数类型是可以的,因为宽泛函数能处理所有Animal,自然能处理Dog。
回到你的代码:
ProtocolListener<ProtocolBlueprint>的参数是[...Data[], sender: number]——这是一个宽泛的参数类型,它能接受任意的Data数组作为args。ProtocolListener<D[K]>的参数是[...D[K]["args"], sender: number]——这是更具体的参数类型,比如countUsers对应的args是空数组,它只能接受空数组作为args。
所以你想把只能处理具体args的函数赋值给能处理任意args的函数类型,这在TypeScript的类型系统里是不安全的,所以报错了。
关于错误里的Source和Target
- Source:指的是你要赋值的
listener的参数类型,也就是[...D[K]["args"], sender: number](比如countUsers对应的[], sender: number)。 - Target:指的是你要赋值到的
implementation的参数类型,也就是[...Data[], sender: number]。
错误说的是,Source的可变参数部分(位置0的元素,也就是args数组)没办法匹配Target的可变参数——Target的args是任意Data数组,但Source的args是固定的、更具体的数组,自然没法“覆盖”Target的要求。
解决办法
如果因为某些原因你必须保持implementation的类型是ProtocolListener<ProtocolBlueprint>,那可以用类型断言来绕过类型检查:
const implementation: ProtocolListener<ProtocolBlueprint> = listener as ProtocolListener<ProtocolBlueprint>;
但要注意,这样做会失去TypeScript的类型保护,你需要自己确保运行时不会出现参数不匹配的情况。
备注:内容来源于stack exchange,提问作者Sonny D




