好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

TypeScript 类型错误何时发生? ~通过子类型化和分解学习TypeScript类型系统~

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" 这样的字符串。此时, number 是 number | string 的子集,对吧?

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

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

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

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

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

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

如果您尝试使用值 HogeFuga ,而实际内容为 Hoge ,则属性 fuga 不存在并出现错误。相反,即使 Hoge 的内容是 HogeFuga , Hoge 也只使用属性 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 
 

同样,结合 Hoge 和 HogeFuga 给出:

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

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

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

类型变量和变异

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 
 

因此,当 A 是 B 的子类型时, 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 
 

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

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

当 A 是 B 的子类型时, Array<A> 是 Array<B> 的子类型 当 A 是 B 的子类型时, Func<B> 是 Func<A> 的子类型

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

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

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 中交谈!

参考

子类型 结构子类型化_TypeScript简介《生存TypeScript》 类型系统介绍第十五章部分类型 变性 关于 TypeScript 的差异——从 30 岁开始编程 数组协方差_TypeScript简介《生存TypeScript》 类型兼容性 - TypeScript 深入探讨 协变和逆变(计算机科学) - 维基百科 严格函数类型 TypeScript_ TSConfig 参考 - 每个 TSConfig 选项的文档 strictFunctionTypes_TypeScript简介《生存TypeScript》 可选的方差注释 宣布 TypeScript 4.7 - TypeScript 《TypeScript for Professionals 简介》读者可以了解最新资讯的文章 Optional Variance Annotations 的行为说明 - Object.create(null)

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


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

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

查看更多关于TypeScript 类型错误何时发生? ~通过子类型化和分解学习TypeScript类型系统~的详细内容...

  阅读:57次