首页 > 解决方案 > 具有未知参数的函数不能分配给具有字符串参数的函数

问题描述

我正在使用打字稿,我定义了以下接口:

export interface BaseProtocol {
    [key: string]: (payload: unknown) => Promise<unknown>;
}

现在我想定义一个协议:

import { BaseProtocol } from './BaseProtocol';

export interface AppProtocol extends BaseProtocol {
    action(payload: string): Promise<string>;
}

不幸的是,我收到以下错误:

Property 'action' of type '(payload: string) => Promise<string>' is not assignable to string index type '(payload: unknown) => Promise<unknown>'.ts(2411)

我不愿意使用任何,因为它损害了协议使用的类型安全。知道如何解决这个问题吗?

标签: typescripttypescript-generics

解决方案


该错误是有效的;如果你有接口AB extends A,那么如果我想要一个A,你可以给我一个B。但是根据您的定义,这会中断:

function callBaseProtocol(bp: BaseProtocol) {
    return bp.action(12345);
}

function callAppProtocol(ap: AppProtocol) {
    callBaseProtocol(ap); // oops!
}

该函数callBaseProtocol()没有错误。的定义BaseProtocol每个字符串值的键都是一个函数,它接受一个类型的值unknown并返回一个Promise<unknown>. 具体来说, a 的action属性BaseProtocol必须是一个接受 type 值的函数unknown。因此,您可以将其传递给number.

该功能callAppProtocol()也没有错误。在这里,我们调用callBaseProtocol()并传递一个AppProtocol. 这应该是可以接受的,因为AppProtocol extends BaseProtocol.

但是,如果 aAppProtocol有一个action期望 a 的属性string并在其上调用一些string特定的方法,例如x => x.toUpperCase(),这将爆炸。错误在于AppProtocol实际上没有正确扩展BaseProtocol.


这种可能令人惊讶的现象被称为参数逆变,如果X是 的子类型Y,则函数(y: Y)=>any是 的子类型(x: X)=>any反之则不然。子类型化的方向对于函数参数是相反的,而“相反”就是“逆变”的意思。

这种行为是通过--strictFunctionTypes编译器选项在 TypeScript 中引入的,是一种类型安全性改进。


那么你怎么能处理这个呢?假设您想要提高类型安全性而不是降低类型安全性(您提到不想使用any),那么您BaseProtocol的定义可能并不正确。unknowns也许它应该是一个通用接口,而不是一个字符串索引签名和传递,它表示输入和输出之间更复杂的关系。也许是这样的:

export type BaseProtocol<T> = {
    [K in keyof T]: (payload: T[K]) => Promise<T[K]>;
}

这就是说 aBaseProtocol依赖于另一种类型T,并且 aBaseProtocol<T>对 的每个属性都有一个方法T,该方法接受该属性值类型的参数,并返回Promise该类型的 a。

然后AppProtocol可以简单定义为:

export interface AppProtocol extends BaseProtocol<{ action: string }> {
}

这解决了之前的问题,因为编译器不允许您在参数上调用 aBaseProtocol<T>action()方法,number除非它知道它T具有actiontype 的属性number

function callBaseProtocol<T>(bp: BaseProtocol<T>) {
    return bp.action(12345); // error! might not have an action
}

还有一种混合方法,它不假设每个函数的返回值是与其输入相同类型的承诺:

export type BaseProtocol<T> = {
    [K in keyof T]: (payload: T[K]) => unknown;
}

export interface AppProtocol extends BaseProtocol<{ action: string }> {
    action(x: string): Promise<string>;
}

好的,希望有帮助;祝你好运!

Playground 代码链接


推荐阅读