You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

TypeScript报错‘Source provides no match for variadic element at position 0 in target.(2322)’原因及代码类型赋值问题咨询

TypeScript报错‘Source provides no match for variadic element at position 0 in target.(2322)’原因及代码类型赋值问题咨询

我来帮你捋清楚这个问题哈!先看看你的代码场景:

你定义了一套协议相关的类型,然后在registerInProtocol函数里,想把传入的listener(类型是ProtocolListener<D[K]>)赋值给一个类型更宽泛的implementationProtocolListener<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

火山引擎 最新活动