不强制执行方法签名

No enforcement for method signature

如果我在接口中定义一个方法然后稍后实现它,如果接口使用方法签名(它适用于 属性 签名),则不会强制执行它的参数类型。

example

它看起来很像一个错误,是吗?是故意的吗?有没有办法在不将我们所有的功能更改为 属性 签名的情况下解决它?

它与这个 不同,因为在这个问题中,接口的方法接受一些参数并且实现可以在没有它的情况下工作(这是合乎逻辑的)。但是这里的实现希望收到一个扩展参数,该参数 在接口

中不存在

这是故意的。关于函数参数和方差的一些背景知识(如果你已经知道可以跳过):

如果我要一个接受 string 的函数,而你给我一个接受 unknown 的函数,我会很高兴。我可以安全地将 string 传递给该函数,它会接受它,因为 string 可分配给 unknown。这是逆变,因为可分配性的方向切换:string可分配给unknown,因此(x: unknown) => void可分配给(x: string) => void

将此与 协方差 进行比较,可分配方向保持不变:"foo" 可分配给 string,因此 (x: "foo") => void可分配给 (x: string) => void?不,这不对。如果我要求一个接受 string 的函数,而你给我一个只接受字符串文字 "foo" 的函数,如果我使用它,我可能会不高兴。如果我向该函数传递一个 string 而恰好不是 "foo",那么该函数将不会接受它。

因此,逆变地检查函数参数是类型安全的,而协变地检查它们不是类型安全的。那么 TypeScript 是做什么的呢?好吧,在 TypeScript 2.6 之前,编译器实际上允许 both。也就是说,它检查了所有函数和方法参数 bivariantly

为什么要这样做?其中一个问题与修改对象的方法有关。考虑链接的常见问题解答中的示例:数组的 push() 方法。我们通常希望 string[] 可以分配给 unknown[]。如果我只从这样的数组中读取,这是非常安全的。我要一个 unknown[];你给我一个string[];我从中读取了 unknown 值(恰好是 string 对象,但没关系)。但是写入数组是不安全的。如果我调用 array.push(123),如果 arrayunknown[] 应该没问题,但如果是 string[] 就非常糟糕。如果只对 push() 的参数进行逆变检查,这将加强安全性,不允许将 string[] 分配给 unknown[]。但是,正如他们在常见问题解答中所说,那将是 "incredibly annoying"。相反,它们允许允许函数和方法参数双向变化的不安全但方便的约定。


回到这个问题的答案:幸运的是,对于我们这些更喜欢类型安全的人来说,TypeScript 2.6 引入了 --strictFunctionTypes flag,启用后,函数参数类型只会被逆变检查双变量的。

但是 Array 和其他经常协变使用的内置 类 仍然存在问题。为了防止 --strictFunctionTypes 变成 "incredibly annoying",权衡是 function 参数被逆变检查,但是 method 参数仍然是双变量检查。所以从 TS2.6 开始,编译器在类型检查参数时关心方法签名和函数签名之间的区别。

因此具有以下界面:

interface Example {
    func: (x: string) => void;
    method(x: string): void;
}

您会得到以下行为(在启用 --strictFunctionTypes 的 TS2.6+ 中):

const contravariant: Example = {
    func(x: unknown) { }, // okay!
    method: (x: unknown) => { } // okay!
}

const covariant: Example = {
    func(x: "foo") { }, // error!
    method: (x: "foo") => { } // okay!
}

请注意,在 contravariantcovariant 中,func 实现的 作为方法,而 method实现作为函数值属性,但根据Example接口中的规则,它们仍然检查func 有一个函数签名并严格检查,method 有一个方法签名并松散检查。


那么,有没有办法在不将所有函数更改为 属性 签名的情况下解决这个问题?可能没什么了不起的。您可以尝试以编程方式转换签名,如下所示:

type MethodsToFunctionProps<T> = {
    [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => R : T[K]
}

在这种情况下有效:

const covariantFixed: MethodsToFunctionProps<Example> = {
    func(x: "foo") { }, // error!
    method: (x: "foo") => { } // error!
}

但可能存在极端情况,所以我不知道这对您来说是否值得。


无论如何,希望对您有所帮助;祝你好运!

Link to code