gpt4 book ai didi

typescript - 为什么在 TypeScript 中接口(interface)中可能的数字值可以转换为类实现中不可能的数字值?

转载 作者:行者123 更新时间:2023-12-04 02:35:42 24 4
gpt4 key购买 nike

今天我遇到了一个意外的 TypeScript 编译器行为。我想知道这是错误还是功能。可能这将是最后一个,但我想知道它背后的基本原理。

如果我声明一个接口(interface)方法,其参数可以是一个string | number,并创建一个实现该接口(interface)的类,然后类方法可以使该参数仅为 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 的可悲/可笑的事实是它不是完全类型安全的。有些功能是故意不健全的,在人们认为健全性会阻碍生产力的地方。参见 "a note on soundness" in the TypeScript Handbook .您遇到过这样的功能之一:method parameter bivariance .

当您的函数或方法类型接受 A 类型的参数时,实现或扩展它的唯一类型安全方法是接受父类(super class)型的参数BA .这称为参数 contravariance : 如果 A延伸B , 然后 ((param: B) => void) extends ((param: A) => void) .函数的子类型关系与其参数的子类型关系相反。所以给出{ hello(value: string | number): void } , 用 { hello(value: string | number | boolean): void } 来实现它是安全的或 { hello(value: unknown): void} .

但你用 { hello(value: string): void} 实现了它;实现接受声明参数的子类型。这就是协方差(函数及其参数的子类型关系相同),正如您所指出的,这是不安全的。 TypeScript 接受安全逆变实现和不安全协变实现:这称为双变

那么为什么在方法中允许这样做?答案是因为很多常用类型都有协变方法参数,强制逆变会导致这些类型无法形成子类型层次结构。来自 the FAQ entry on parameter bivariance 的激励示例是Array<T> .想到Array<string>真是太方便了作为 Array<string | number> 的子类型.毕竟,如果你向我要一个 Array<string | number> ,我递给你["a", "b", "c"] ,那应该可以接受吧?好吧,如果你对方法参数很严格的话就不会了。毕竟,Array<string | number>应该让你push(123)对它,而 Array<string>不应该。出于这个原因,方法参数协变是允许的。


那你能做什么?在 TypeScript 2.6 之前,所有函数都是这样操作的。但后来他们介绍了 --strictFunctionTypes compiler flag .如果您启用它(您应该启用),那么函数参数类型会被协变检查(安全),而方法参数类型仍然会被双变检查(不安全)。

类型系统中函数和方法之间的区别相当微妙。类型 { a(x: string): void }{ a: (x: string) => void }除第一种类型 a 外,其他都是一样的是一种方法,在第二个中,a是函数值属性。因此 x在第一种类型中将进行双变量检查,x在第二种类型中将被逆变检查。但除此之外,它们的行为基本相同。您可以将方法实现为函数值属性,反之亦然。

这导致了以下可能的问题解决方案:

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 的父类(super class)型,这些错误消失了:

class FooClass implements Foo {
hello(value: string | number | boolean) { // okay now
console.log(`hello ${value}`)
}
}

好的,希望对你有帮助;祝你好运!

Playground link to code

关于typescript - 为什么在 TypeScript 中接口(interface)中可能的数字值可以转换为类实现中不可能的数字值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61979247/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com