TypeScript 类型错误何时发生?

TypeScript 是一种静态类型语言。
它通过编译、编辑器扩展等进行类型检查,并在执行前显示类型错误。

什么时候出现类型错误?
为了获得线索,让我们看一下 TypeScript 中的一个真实类型错误。

const x: number = "number"
//    ^
// Type 'string' is not assignable to type 'number'.

string 类型变为 number 类型任务你不能。

换句话说,当“右侧的值类型”无法分配给“左侧的变量类型”时,似乎发生了错误。
什么情况下可以赋值,什么情况下不能赋值?

如果类型相同,当然可以分配。但这还不是全部。

答案是“右侧值的类型”与“左侧变量的类型”相同。部分类型如果可以分配
在这种情况下,类型检查可以通过。

在本文中部分类型和相关的变性并在 TypeScript 4.7 中添加可选的方差注释本节说明。

TypeScript 中的部分类型正是结构亚型(结构子类型)。
欲了解更多信息,请参阅这篇文章:结构子类型化_TypeScript简介《生存TypeScript》

部分类型

我们经常用集合来描述类型,所以如果我们在这里照样用集合来描述子类型,那么子类型就是子集。

比如number | string这样的联合类型是一个集合,其值为33-4这样的数字和"Hello World""TypeScript"这样的字符串。此时,numbernumber | string 的子集,对吧?

也就是说,number 类型是number | string 类型的子类型并且是可赋值的!

const x: number = 1
const y: number | string = x
// => OK!

让我们看另一个例子。接下来是对象类型。
假设我们有以下两种对象类型: HogeHogeFuga 哪个子类型?

type Hoge = { hoge: number }
type HogeFuga = { hoge: number, fuga: string }

乍一看,Hoge 似乎是HogeFuga 的子类型,但事实恰恰相反,HogeFugaHoge 的子类型。
我认为如果你想象它会如何使用它会更容易理解。

当您使用Hoge 类型时,您会遇到一种情况,您希望从其值中提取属性hoge
HogeFuga 类型的属性需要 hogefuga

如果您尝试使用值HogeFuga,而实际内容为Hoge,则属性fuga 不存在并出现错误。相反,即使Hoge 的内容是HogeFugaHoge 也只使用属性hoge,所以它工作正常。

事实上,如果你尝试分配每个,前者会导致错误,而后者不会有问题。

const h : Hoge     = { hoge: 1 }
const hf: HogeFuga = { hoge: 1, fuga: "a" }

const x: HogeFuga = h  // => Property 'fuga' is missing in type 'Hoge' but required in type 'HogeFuga'.
const y: Hoge     = hf // => OK

同样,结合 HogeHogeFuga 给出:

  • Hoge:具有hoge 属性的对象的类型(它也可以有其他属性!)
  • HogeFuga:具有hogefuga 属性的对象类型

Hoge 是一个比 HogeFuga 更广泛的集合,即使综合考虑也是如此,所以 HogeFugaHoge 的子类型。

这样,如果你把一个类型看成一个集合,并根据它是否是一个子集来判断一个子类型,那么类型检查在大多数情况下都会通过。
我们将在下一章中介绍一个稍微复杂的案例。

类型变量和变异

TypeScript 具有带有类型变量的类型,例如 Array<T>。 (对于Array<T>T 是一个类型变量)
对于此类类型,它们是否是类型变量的子类型变得很重要。

比如Array<number | string>Array<number>呢?
Array<number> 不能替换为 Array<number | string>

const arrNS: Array<number | string> = [1, "two", 3]
const arrN: Array<number> = arrNS // => Error
//    ^^^^
// Type '(string | number)[]' is not assignable to type 'number[]'.
//   Type 'string | number' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.

另一方面,Array<number> 可以分配给Array<number | string>

const arrN: Array<number> = [1,2,3]
const arrNS: Array<number | string> = arrN // => OK

因此,当AB 的子类型时,Array<A> 成为Array<B> 的子类型。
如果某个类型的类型变量是子类型,那么该类型也一定是子类型吗?

答案是不。让我们看另一个例子。

作为类型变量,考虑给定函数参数类型的Func<T> 类型。

type Func<T> = (x: T) => number

让我们用Func<number> 代替Func<number | string>

const funcN: Func<number> = (x: number) => 1
const funcNS: Func<number | string> = funcN
//    ^^^^^^
// Type 'Func<number>' is not assignable to type 'Func<string | number>'.
//   Type 'string | number' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.

我有一个错误。
现在让我们用Func<number | string> 代替Func<number>

const funcNS: Func<number | string> = (x: number | string) => 1
const funcN: Func<number> = funcNS // OK

已分配!
numbernumber | string 的子类型,但Func<number | string> 似乎是Func<number> 的子类型。

总而言之,它看起来像这样:

  • AB 的子类型时,Array<A>Array<B> 的子类型
  • AB 的子类型时,Func<B>Func<A> 的子类型

这样子类型关系根据类型变量的使用方式而改变的属性变性称为(方差)。
有四种类型的更改:

  • 协方差(协变)
    • AB 的子类型时,Type<A>Type<B> 的子类型
  • 逆变的(逆变)
    • BA 的子类型时,Type<A>Type<B> 的子类型
  • 双重变化(双变量)
    • AB 的子类型或BA 的子类型时,则Type<A>Type<B> 的子类型
  • 不可变,不可变(不变的,不变的)1
    • Type<A>Type<B> 的子类型,仅当 AB 属于同一类型时

Array<T> 可以表示为协变,Func<T> 可以表示为逆变。
TypeScript 在许多情况下基本上是协变的,而在函数参数等情况下是逆变的。

严格函数类型

我解释了函数参数是逆变的,但实际上,在 TypeScript 中,默认情况下,函数参数是双重变化是。
TypeScript 2.6 中添加的选项 strictFunctionTypes 使其具有逆变性。
strict: true 也打开了strictFunctionTypes,所以如果你对阅读本文的类型感兴趣,你应该没问题,但要小心。

如果函数参数是双变量的怎么办?

const funcN: Func<number> = (x: number) => Math.abs(x)
const funcNS: Func<number | string> = funcN
funcNS("apple")

因为是双变量,上面的赋值不会出错,所以执行Math.abs("apple")
这样如果函数参数不是逆变的,在运行时可能会出错,所以建议开启strictFunctionTypes

可选的方差注释

最后,我们介绍 TypeScript 4.7 中添加的语法 Optional Variance Annotations。
顾名思义,它允许您对变体进行注释。

逆变参数写in,协变参数写out,如下所示。

type Func<in T, out S> = (x: T) => S

因为它是可选的,所以无论它是否存在,类型都不会改变,但是通过这样描述它,可变性就变得清晰了。
如果您进行了不正确的注释,它也会给出错误。

type Func<out T, in S> = (x: T) => S // => Error
//        ^^^^^  ^^^^
// Type 'Func<sub-T, S>' is not assignable to type 'Func<super-T, S>' as implied by variance annotation.
//   Types of parameters 'x' and 'x' are incompatible.
//      Type 'super-T' is not assignable to type 'sub-T'.
// Type 'Func<T, super-S>' is not assignable to type 'Func<T, sub-S>' as implied by variance annotation.
//   Type 'super-S' is not assignable to type 'sub-S'.

最后

感谢您阅读到最后!
如果你想听更多的细节,让我们直接在 Dev Talk 中交谈!

参考

  1. 我认为 immutable 和 invariable 是指同一个属性的词,但我都写了这两个词,因为网站上的符号不同。参考:https://togetter.com/li/66427


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308624374.html

相关文章:

  • 2021-11-11
  • 2022-12-23
  • 2021-10-27
  • 2021-07-17
  • 2021-12-11
  • 2021-11-15
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-07-15
  • 2021-08-21
  • 2022-01-28
  • 2021-09-16
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案