Coe*_*oen 3 type-systems language-design implicit-conversion typescript
今天我遇到了一个意想不到的 TypeScript 编译器行为。我想知道这是一个错误还是一个功能。可能它会是最后一个,但我想知道它背后的原理。
如果我声明一个接口方法,其参数可以是 a string | number,并创建一个实现该接口的类,则该类方法只能使该参数成为string。这会导致类实现不需要数字,但编译器允许传递该数字的情况。为什么这是允许的?
interface Foo {
hello(value: string | number): void
}
class FooClass implements Foo {
hello(value: string) { //notice the missing 'number'
console.log(`hello ${value}`)
}
}
const x = new FooClass()
x.hello("me")
//x.hello(42) this gives a compile error
const y: Foo = x
y.hello(42)
Run Code Online (Sandbox Code Playgroud)
关于 TypeScript 的可悲/有趣的事实是它不是完全类型安全的。有些功能是故意不健全的,在那些认为健全会阻碍生产力的地方。请参阅TypeScript 手册中的“关于健全性的说明”。您遇到了一个这样的功能:方法参数双方差。
当您有一个函数或方法类型接受类型为 的参数时A,实现或扩展它的唯一类型安全方法是接受 的超类型 B的参数A。这称为参数逆变:如果A扩展B,则((param: B) => void) extends ((param: A) => void). 函数的子类型关系与其参数的子类型关系相反。因此{ hello(value: string | number): void },使用{ hello(value: string | number | boolean): void }or实现它是安全的{ hello(value: unknown): void}。
但是你用{ hello(value: string): void}; 实现正在接受声明参数的子类型。这是协方差(函数及其参数的子类型关系相同),正如您所指出的,这是不安全的。打字稿接受两个安全逆变实施和不安全协执行:这就是所谓的bivariance。
那么为什么这在方法中是允许的呢?答案是因为很多常用的类型都有协变的方法参数,强制逆变会导致这些类型无法形成子类型层次结构。关于参数二元性的 FAQ 条目中的激励示例是Array<T>。将其Array<string>视为,例如,的子类型非常方便Array<string | number>。毕竟,如果你向我要一个Array<string | number>,我递给你["a", "b", "c"],那应该是可以接受的,对吧?好吧,如果您对方法参数很严格,则不会。毕竟,anArray<string | number>应该让你push(123)这样做,而 anArray<string>不应该。由于这个原因,方法参数协方差是允许的。
所以,你可以做什么?在 TypeScript 2.6 之前,所有函数都以这种方式运行。但随后他们引入了--strictFunctionTypes编译器标志。如果启用(并且应该启用),则函数参数类型会进行协变(安全)检查,而方法参数类型仍会进行双变(不安全)检查。
类型系统中的函数和方法之间的区别是相当微妙的。的类型{ a(x: string): void }和{ a: (x: string) => void }是不同的是在第一类型相同的a是一种方法,并且在第二,a是一个函数值属性。因此x,第一种类型中的 将被双变量检查,而x第二种类型中的 将被逆变检查。但除此之外,它们的行为基本相同。您可以将方法实现为函数值属性,反之亦然。
这导致这里问题的以下潜在解决方案:
interface Foo {
hello: (value: string | number) => void
}
Run Code Online (Sandbox Code Playgroud)
现在hello被声明为函数而不是方法类型。但是类的实现仍然可以是一个方法。现在你得到了预期的错误:
class FooClass implements Foo {
hello(value: string) { // error!
// ~~~~~
// string | number is not assignable to string
console.log(`hello ${value}`)
}
}
Run Code Online (Sandbox Code Playgroud)
如果你这样离开它,稍后你会得到一个错误:
const y: Foo = x; // error!
// ~
// FooClass is not a Foo
Run Code Online (Sandbox Code Playgroud)
如果您修复FooClass它以hello()接受 的超类型string | number,那么这些错误就会消失:
class FooClass implements Foo {
hello(value: string | number | boolean) { // okay now
console.log(`hello ${value}`)
}
}
Run Code Online (Sandbox Code Playgroud)
好的,希望有帮助;祝你好运!
| 归档时间: |
|
| 查看次数: |
110 次 |
| 最近记录: |