不强制执行方法签名
No enforcement for method signature
如果我在接口中定义一个方法然后稍后实现它,如果接口使用方法签名(它适用于 属性 签名),则不会强制执行它的参数类型。
它看起来很像一个错误,是吗?是故意的吗?有没有办法在不将我们所有的功能更改为 属性 签名的情况下解决它?
它与这个 不同,因为在这个问题中,接口的方法接受一些参数并且实现可以在没有它的情况下工作(这是合乎逻辑的)。但是这里的实现希望收到一个扩展参数,该参数 在接口
中不存在
这是故意的。关于函数参数和方差的一些背景知识(如果你已经知道可以跳过):
如果我要一个接受 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)
,如果 array
是 unknown[]
应该没问题,但如果是 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!
}
请注意,在 contravariant
和 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!
}
但可能存在极端情况,所以我不知道这对您来说是否值得。
无论如何,希望对您有所帮助;祝你好运!
如果我在接口中定义一个方法然后稍后实现它,如果接口使用方法签名(它适用于 属性 签名),则不会强制执行它的参数类型。
它看起来很像一个错误,是吗?是故意的吗?有没有办法在不将我们所有的功能更改为 属性 签名的情况下解决它?
它与这个
这是故意的。关于函数参数和方差的一些背景知识(如果你已经知道可以跳过):
如果我要一个接受 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)
,如果 array
是 unknown[]
应该没问题,但如果是 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!
}
请注意,在 contravariant
和 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!
}
但可能存在极端情况,所以我不知道这对您来说是否值得。
无论如何,希望对您有所帮助;祝你好运!