成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入 TypeScript 中的子類型、逆變、協變,進階 Vue3 源碼前必須搞懂的

開發 前端
TypeScript 中有很多地方涉及到子類型 subtype、父類型 supertype 的概念,如果搞不清這些概念,那么很可能被報錯搞得無從下手,或者在寫一些復雜類型的時候看到別人可以這么寫,但是不知道為什么他可以生效。

 [[336024]]

本文轉載自微信公眾號「 前端從進階到入院」,作者 ssh前端 。轉載本文請聯系 前端從進階到入院公眾號。

前言

TypeScript 中有很多地方涉及到子類型 subtype、父類型 supertype 的概念,如果搞不清這些概念,那么很可能被報錯搞得無從下手,或者在寫一些復雜類型的時候看到別人可以這么寫,但是不知道為什么他可以生效。(就是我自己沒錯了)

子類型

比如考慮如下接口:

  1. interface Animal { 
  2.   age: number 
  3.  
  4. interface Dog extends Animal { 
  5.   bark(): void 

在這個例子中,Animal 是 Dog 的父類,Dog是Animal的子類型,子類型的屬性比父類型更多,更具體。

  • 在類型系統中,屬性更多的類型是子類型。
  • 在集合論中,屬性更少的集合是子集。

也就是說,子類型是父類型的超集,而父類型是子類型的子集,這是直覺上容易搞混的一點。

記住一個特征,子類型比父類型更加具體,這點很關鍵。

可賦值性 assignable

assignable 是類型系統中很重要的一個概念,當你把一個變量賦值給另一個變量時,就要檢查這兩個變量的類型之間是否可以相互賦值。

  1. let animal: Animal 
  2. let dog: Dog 
  3.  
  4. animal = dog // ✅ok 
  5. dog = animal // ❌error! animal 實例上缺少屬性 'bark' 

從這個例子里可以看出,animal 是一個「更寬泛」的類型,它的屬性比較少,所以更「具體」的子類型是可以賦值給它的,因為你是知道 animal 上只有 age 這個屬性的,你只會去使用這個屬性,dog 上擁有 animal 所擁有的一切類型,賦值給 animal 是不會出現類型安全問題的。

反之,如果 dog = animal,那么后續使用者會期望 dog 上擁有 bark 屬性,當他調用了 dog.bark() 就會引發運行時的崩潰。

從可賦值性角度來說,子類型是可以賦值給父類型的,也就是 父類型變量 = 子類型變量 是安全的,因為子類型上涵蓋了父類型所擁有的的一切屬性。

當我初學的時候,我會覺得 T extends {} 這樣的語句很奇怪,為什么可以 extends 一個空類型并且在傳遞任意類型時都成立呢?當搞明白上面的知識點,這個問題也自然迎刃而解了。

在函數中的運用

假設我們有這樣的一個函數:

  1. function f(val: { a: number; b: number }) 

有這樣兩個變量:

  1. let val1 = { a: 1 } 
  2. let val2 = { a: 1, b: 2, c: 3 } 

調用 f(val1) 是會報錯的,比較顯而易見的來看是因為缺少屬性 b,而函數 f 中很可能去訪問 b 屬性并且做一些操作,比如 b.substr(),這就會導致崩潰。

換成上面的知識點來看,val1 對應的類型是{ a: number },它是 { a: number, b: number } 的父類型,調用 f(val1) 其實就相當于把函數定義中的形參 val 賦值成了 val1, 把父類型的變量賦值給子類型的變量,這是危險的。

反之,調用 f(val2) 沒有任何問題,因為 val2 的類型是 val類型的子類型,它擁有更多的屬性,函數有可能使用的一切屬性它都有。

假設我現在要開發一個 redux,在聲明 dispatch 類型的時候,我就可以這樣去做:

  1. interface Action { 
  2.   type: string 
  3.  
  4. declare function dispatch<T extends Action>(action: T) 

這樣,就約束了傳入的參數一定是 Action 的子類型。也就是說,必須有 type,其他的屬性有沒有,您隨意。

在聯合類型中的運用

學習了以上知識點,再看聯合類型的可賦值性,乍一看會比較反直覺, 'a' | 'b' | 'c' 是 'a' | 'b' 的子類型嗎?它看起來屬性更多誒?其實正相反,'a' | 'b' | 'c' 是 'a' | 'b' 的父類型。因為前者比后者更「寬泛」,后者比前者更「具體」。

  1. type Parent = 'a' | 'b' | 'c' 
  2. type Son = 'a' | 'b' 
  3.  
  4. let parent: Parent 
  5. let son: Son 
  6.  
  7. parent = son // ✅ok 
  8. son = parent // ❌error! parent 有可能是 'c' 

這里 son 是可以安全的賦值給 parent 的,因為 son 的所有可能性都被 parent 涵蓋了。

而反之則不行,parent 太寬泛了,它有可能是 'c',這是 Son 類型 hold 不住的。

這個例子看完以后,你應該可以理解為什么 'a' | 'b' extends 'a' | 'b' | 'c' 為 true 了,在書寫 conditional types的時候更加靈活的運用吧。

逆變和協變

先來段維基百科的定義[1]:

協變與逆變(Covariance and contravariance )是在計算機科學中,描述具有父/子型別關系的多個型別通過型別構造器、構造出的多個復雜型別之間是否有父/子型別關系的用語。

描述的比較晦澀難懂,但是用我們上面的動物類型的例子來解釋一波,現在我們還是有 Animal 和 Dog 兩個父子類型。

協變(Covariance)

那么想象一下,現在我們分別有這兩個子類型的數組,他們之間的父子關系應該是怎么樣的呢?沒錯,Animal[] 依然是 Dog[] 的父類型,對于這樣的一段代碼,把子類型賦值給父類型依然是安全的:

  1. let animals: Animal[] 
  2. let dogs: Dog[] 
  3.  
  4. animals = dogs 
  5.  
  6. animals[0].age // ✅ok 

轉變成數組之后,對于父類型的變量,我們依然只會去 Dog 類型中一定有的那些屬性。

那么,對于 type MakeArray = T[] 這個類型構造器來說,它就是 協變(Covariance) 的。

逆變(Contravariance)

有這樣兩個函數:

  1. let visitAnimal = (animal: Animal) => void; 
  2. let visitDog = (dog: Dog) => void; 

animal = dog 是類型安全的,那么 visitAnimal = visitDog 好像也是可行的?其實不然,想象一下這兩個函數的實現:

  1. let visitAnimal = (animal: Animal) => { 
  2.   animal.age 
  3.  
  4. let visitDog = (dog: Dog) => { 
  5.   dog.age 
  6.   dog.bark() 

由于 visitDog 的參數期望的是一個更具體的帶有 bark 屬性的子類型,所以如果 visitAnimal = visitDog 后,我們可能會用一個不帶 bark 屬性的普通的 animal 類型來傳給 visitDog。

  1. visitAnimal = visitDog 
  2.  
  3. let animal = { age: 5 } 
  4.  
  5. visitAnimal(animal) // ❌ 

這會造成運行時錯誤,animal.bark 根本不存在,去調用這個方法會引發崩潰。

但是反過來,visitDog = visitAnimal 卻是完全可行的。因為后續調用方會傳入一個比 animal 屬性更具體的 dog,函數體內部的一切訪問都是安全的。

在對 Animal 和 Dog 類型分別調用如下的類型構造器之后:

  1. type MakeFunction<T> = (arg: T) => void 

父子類型關系逆轉了,這就是 逆變(Contravariance)。

在 TS 中

當然,在 TypeScript 中,由于靈活性等權衡,對于函數參數默認的處理是 雙向協變 的。也就是既可以 visitAnimal = visitDog,也可以 visitDog = visitAnimal。在開啟了 tsconfig 中的 strictFunctionType 后才會嚴格按照 逆變 來約束賦值關系。

 

責任編輯:武曉燕 來源: 前端從進階到入院
相關推薦

2009-08-03 18:24:28

C# 4.0協變和逆變

2022-04-18 20:12:03

TypeScript靜態類型JavaScrip

2009-05-27 11:30:20

C#Visual Stud協變

2011-01-14 10:27:18

C#.netasp.net

2012-03-13 09:32:15

C#協變

2023-01-29 09:15:42

2020-09-17 07:08:04

TypescriptVue3前端

2021-05-26 10:40:28

Vue3TypeScript前端

2021-07-29 12:05:18

Vue3Api前端

2020-09-29 06:37:30

Java泛型

2021-09-22 07:57:23

Vue3 插件Vue應用

2021-11-19 09:29:25

項目技術開發

2021-12-08 09:09:33

Vue 3 Computed Vue2

2021-11-26 05:59:31

Vue3 插件Vue應用

2021-12-13 00:54:14

組件Vue3Setup

2020-02-11 14:14:52

this函數

2020-10-25 18:43:20

VueTypeScript前端

2013-10-31 09:36:43

程序員程序高手

2024-07-01 13:45:18

2024-03-22 08:57:04

Vue3Emoji表情符號
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成在线人视频免费视频 | 91在线电影| 美国一级黄色片 | 欧美黑人一级爽快片淫片高清 | 国产精品久久久久久亚洲调教 | 91精品国产综合久久婷婷香蕉 | 夏同学福利网 | 国产成人99久久亚洲综合精品 | 99热碰| 日韩精品成人网 | 亚洲 中文 欧美 日韩 在线观看 | 嫩草视频在线看 | 亚洲乱码一区二区三区在线观看 | av在线一区二区 | 久久久久久国 | 天天射中文| 成人在线视频免费观看 | 午夜视频网 | 国产一区久久 | 综合网视频 | 97avcc| 欧美精品一区二区三区在线 | 久久噜噜噜精品国产亚洲综合 | 国产乡下妇女做爰 | 亚洲小视频在线观看 | 特一级毛片| 国产1区2区3区 | 日韩视频在线一区二区 | 国产99精品| 日韩成人在线视频 | 精品一区二区三区在线视频 | 91久久久久久久久久久久久 | 免费黄色a级毛片 | 亚洲激情在线 | 国产剧情一区二区三区 | av片网站 | 国产精品视频网站 | 亚洲精品一区二区在线 | 四虎最新地址 | 久久精品亚洲成在人线av网址 | 97人人澡人人爽91综合色 |