如果我在接口内定义一个方法,然后稍后实现它,则如果接口使用方法签名(它适用于属性签名),则不会强制执行其参数类型。
它看起来很像一个错误,是吗?是故意的吗?有没有办法在不将我们所有的函数更改为属性签名的情况下解决它?
它与这个问题不同,因为在这个问题中,接口的方法接受一些参数并且实现可以在没有它的情况下工作(这是合乎逻辑的)。但是这里的实现期望接收接口中不存在的扩展参数
如果我在接口内定义一个方法,然后稍后实现它,则如果接口使用方法签名(它适用于属性签名),则不会强制执行其参数类型。
它看起来很像一个错误,是吗?是故意的吗?有没有办法在不将我们所有的函数更改为属性签名的情况下解决它?
它与这个问题不同,因为在这个问题中,接口的方法接受一些参数并且实现可以在没有它的情况下工作(这是合乎逻辑的)。但是这里的实现期望接收接口中不存在的扩展参数
这是故意的。关于函数参数和方差的一些背景知识(如果您已经知道,可以跳过):
如果我要求一个接受 a 的函数string
,而你给我一个接受 a 的函数unknown
,我会很高兴。我可以安全地传递该函数 astring
并且它会接受它,因为它string
可以分配给unknown
. 这是逆变,因为可分配性切换的方向:string
可分配给unknown
,因此(x: unknown) => void
可分配给(x: string) => void
。
将此与covariance进行比较,其中可分配性的方向保持不变:"foo"
可分配给string
,因此(x: "foo") => void
可分配给(x: string) => void
? 不,这不对。如果我要求一个接受 a 的函数string
,而你给我一个只接受字符串字面量的函数,"foo"
如果我使用它,我可能会不高兴。如果我传递的函数 astring
不是"foo"
,那么该函数将不接受它。
因此,以逆变方式检查函数参数是类型安全的,而以协变方式检查它们不是类型安全的。那么 TypeScript 是做什么的呢?好吧,在 TypeScript 2.6 之前,编译器实际上同时允许. 也就是说,它以双变量方式检查所有函数和方法参数。
为什么会这样做?其中一个问题与修改对象的方法有关。考虑链接的常见问题解答中的示例:push()
数组方法。我们通常希望 astring[]
可分配给unknown[]
。如果我只从这样的数组中读取,这是非常安全的。我要一个unknown[]
;你给我一个string[]
;我从中读取,提取unknown
值(恰好是string
对象,但这很好)。但是写入数组是不安全的。如果我打电话array.push(123)
,如果是的话应该没问题,但如果array
是unknown[]
的话就很糟糕了string[]
。如果仅以逆变方式检查参数 to push()
,这将强制执行安全性,不允许string[]
分配给unknown[]
. 但是,正如他们在常见问题解答中所说,那将是“令人难以置信的烦人”。相反,它们允许不安全但不方便的约定,即允许函数和方法参数以两种方式变化。
回到这个问题的答案:幸运的是,对于我们这些喜欢更多类型安全性的人来说,TypeScript 2.6 引入了一个--strictFunctionTypes
标志,当启用该标志时,会导致函数参数类型仅以逆变方式而不是双变量方式进行检查。
但是对于Array
经常协变使用的其他内置类仍然存在这个问题。为了防止--strictFunctionTypes
“令人难以置信的烦人”,权衡是函数参数被逆变检查,但方法参数仍然被双变量检查。所以从 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!
}
注意在contravariant
and中covariant
, 实现为方法,实现为函数值属性,但还是按照接口中的规则进行检查:有函数签名,严格检查,有方法签名,检查松散地。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!
}
但可能会有边缘情况,所以我不知道这对你是否值得。
无论如何,希望有所帮助;祝你好运!