今天,我遇到了意外的TypeScript编译器行为。我想知道这是错误还是功能。也许这将是最后一个,但是我想知道其背后的理由。
如果我声明一个参数可以为的接口方法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)
关于TypeScript的可悲的事实是,它不是完全类型安全的。在感觉到稳健性会阻碍生产率的地方,某些功能是故意不健全的。请参阅《 TypeScript手册》中的“关于健全性的说明”。您已经遇到了一个这样的功能:方法参数bivariance。
当你有一个接受类型的参数的函数或方法类型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"]
,那应该可以接受,对吧?好吧,如果您对方法参数严格要求的话,那就不用了。毕竟,一个Array<string | number>
应该让您push(123)
使用它,而一个Array<string>
不应该。因此,方法参数协方差是允许的。
所以,你可以做什么?在TypeScript 2.6之前,所有功能都以这种方式起作用。但是随后他们引入了--strictFunctionTypes
编译器标志。如果启用了(并且应该启用),则将对函数参数类型进行协变量检查(安全),而对方法参数类型进行双变量检查(不安全)。
类型系统中的函数和方法之间的差异相当细微。类型{ a(x: string): void }
和{ a: (x: string) => void }
相同,除了第一种a
是方法,第二种a
是函数值属性。因此x
,将对第一类型的in进行双变量检查,而x
对第二类型的in将进行反检查。除此之外,它们的行为基本相同。您可以将方法实现为函数值属性,反之亦然。
这导致以下可能的解决方案:
interface Foo {
hello: (value: string | number) => void
}
现在hello
被声明为函数而不是方法类型。但是类实现仍然可以是一种方法。现在您得到了预期的错误:
class FooClass implements Foo {
hello(value: string) { // error!
// ~~~~~
// string | number is not assignable to string
console.log(`hello ${value}`)
}
}
而且,如果您这样离开,则稍后会出现错误:
const y: Foo = x; // error!
// ~
// FooClass is not a Foo
如果您进行修复FooClass
以使其hello()
接受的超类型string | number
,则这些错误将消失:
class FooClass implements Foo {
hello(value: string | number | boolean) { // okay now
console.log(`hello ${value}`)
}
}
好的,希望能有所帮助;祝好运!
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句