1. TypeScript 初體驗
TypeScript(簡稱:TS)是 JavaScript 的超集(JS 有的 TS 都有)。
TypeScript = Type + JavaScript(在 JS 基礎之上,為 JS 添加了類型支援)。
TypeScript 是微軟開發的開源程式設計語言,可以在任何運作 JavaScript 的地方運作。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI2EzX4xSZz91ZsAzNfRHLGZkRGZkRfJ3bs92YsAjMfVmepNHLElnN14Wb0UTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxSUhxGatJGbwhFT1Y0Mk9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3YDM4cjYxEzMxgDNxUTY5MTOmRjZyMjNzUGNwEDZyIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
1.1 TypeScript 為什麼要為 JS 添加類型支援?
背景:JS 的類型系統存在“先天缺陷”,JS 代碼中絕大部分錯誤都是類型錯誤(Uncaught TypeError)。問題:增加了找 Bug、改 Bug 的時間,嚴重影響開發效率。
從程式設計語言的動靜來區分,TypeScript 屬于靜态類型的程式設計語言,JS 屬于動态類型的程式設計語言。靜态類型:編譯期做類型檢查; 動态類型:執行期做類型檢查。
代碼編譯和代碼執行的順序:1 編譯 2 執行。
- 對于 JS 來說:需要等到代碼真正去執行的時候才能發現錯誤(晚)。
- 對于 TS 來說:在代碼編譯的時候(代碼執行前)就可以發現錯誤(早)。
- 并且,配合 VSCode 等開發工具,TS 可以提前到在編寫代碼的同時就發現代碼中的錯誤,減少找 Bug、改 Bug 時間
1.2 TypeScript 相比 JS 的優勢
- 更早(寫代碼的同時)發現錯誤,減少找 Bug、改 Bug 時間,提升開發效率。
- 程式中任何位置的代碼都有代碼提示,随時随地的安全感,增強了開發體驗。
- 強大的類型系統提升了代碼的可維護性,使得重構代碼更加容易。
- 支援最新的 ECMAScript 文法,優先體驗最新的文法,讓你走在前端技術的最前沿。
- TS 類型推斷機制,不需要在代碼中的每個地方都顯示标注類型,讓你在享受優勢的同時,盡量降低了成本。
1.3 編譯并運作 TS 代碼
- 建立 hello.ts 檔案(注意:TS 檔案的字尾名為 .ts)。
- 将 TS 編譯為 JS:在終端中輸入指令,tsc hello.ts(此時,在同級目錄中會出現一個同名的 JS 檔案)。
- 執行 JS 代碼:在終端中輸入指令,node hello.js。
說明:所有合法的 JS 代碼都是 TS 代碼,有 JS 基礎隻需要學習 TS 的類型即可。注意:由 TS 編譯生成的 JS 檔案,代碼中就沒有類型資訊了。
2. TypeScript 常用類型
TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且額外的增加了:類型系統。
- 所有的 JS 代碼都是 TS 代碼。
- JS 有類型(比如,number/string 等),但是 JS 不會檢查變量的類型是否發生變化。而 TS 會檢查。TypeScript 類型系統的主要優勢:可以顯示标記出代碼中的意外行為,進而降低了發生錯誤的可能性。
- 類型注解
- 常用基礎類型
2.1 類型注解
示例代碼:
說明:代碼中的 : number 就是類型注解。
作用:為變量添加類型限制。比如,上述代碼中,約定變量 age 的類型為 number(數值類型)。
解釋:約定了類型,就隻能給變量指派該類型的值,否則,就會報錯。
2.2 常用基礎類型概述
可以将 TS 中的常用基礎類型細分為兩類:1. JS原有類型 2 .TS 新增類型。
- JS原有類型
- 原始類型:number/string/boolean/null/undefined/symbol。
- 對象類型:object(包括,數組、對象、函數等對象)。
- TS 新增類型
- 聯合類型、自定義類型(類型别名)、接口、元組、字面量類型、枚舉、void、any 等。
2.3 原始類型
- 原始類型:number/string/boolean/null/undefined/symbol。特點:簡單。這些類型,全按照 JS 中類型的名稱來書寫。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
2.4 數組類型
對象類型:object(包括,數組、對象、函數等對象)。
特點:對象類型,在 TS 中更加細化,每個具體的對象都有自己的類型文法。
數組類型的兩種寫法:(推薦使用 number[] 寫法)
需求:數組中既有 number 類型,又有 string 類型,這個數組的類型應該如何寫?
解釋:| (豎線)在 TS 中叫做聯合類型(由兩個或多個其他類型組成的類型,表示可以是這些類型中的任意一種)。
注意:這是 TS 中聯合類型的文法,隻有一根豎線,不要與 JS 中的或(||)混淆了。
2.5 類型别名
類型别名(自定義類型):為任意類型起别名。
使用場景:當同一類型(複雜)被多次使用時,可以通過類型别名,簡化該類型的使用。
解釋:
- 使用 type 關鍵字來建立類型别名。
- 類型别名(比如,此處的 CustomArray),可以是任意合法的變量名稱。
- 建立類型别名後,直接使用該類型别名作為變量的類型注解即可。
2.6 函數類型
函數的類型實際上指的是:函數參數和傳回值的類型。
為函數指定類型的兩種方式:1 單獨指定參數、傳回值的類型 2 同時指定參數、傳回值的類型。
- 單獨指定參數、傳回值的類型:
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
- 同時指定參數、傳回值的類型:
- 解釋:當函數作為表達式時,可以通過類似箭頭函數形式的文法來為函數添加類型。
- 注意:這種形式隻适用于函數表達式。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
- 如果函數沒有傳回值,那麼,函數傳回值類型為:void。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
使用函數實作某個功能時,參數可以傳也可以不傳。這種情況下,在給函數參數指定類型時,就用到可選參數了。
比如,數組的 slice 方法,可以 slice() 也可以 slice(1) 還可以 slice(1, 3)。
可選參數:在可傳可不傳的參數名稱後面添加 ?(問号)。
注意:可選參數隻能出現在參數清單的最後,也就是說可選參數後面不能再出現必選參數。
2.7 對象類型
JS 中的對象是由屬性和方法構成的,而 TS 中對象的類型就是在描述對象的結構(有什麼類型的屬性和方法)。 對象類型的寫法:
解釋:
直接使用 {} 來描述對象結構。屬性采用屬性名: 類型的形式;方法采用方法名(): 傳回值類型的形式。
如果方法有參數,就在方法名後面的小括号中指定參數類型(比如:greet(name: string): void)。
在一行代碼中指定對象的多個屬性類型時,使用 ;(分号)來分隔。
如果一行代碼隻指定一個屬性類型(通過換行來分隔多個屬性類型),可以去掉 ;(分号)。
方法的類型也可以使用箭頭函數形式(比如:{ sayHi: () => void })。
對象的屬性或方法,也可以是可選的,此時就用到可選屬性了。
比如,我們在使用 axios({ … }) 時,如果發送 GET 請求,method 屬性就可以省略。
可選屬性的文法與函數可選參數的文法一緻,都使用 ?(問号)來表示。
2.8 接口
當一個對象類型被多次使用時,一般會使用接口(interface)來描述對象的類型,達到複用的目的。
解釋:
- 使用 interface 關鍵字來聲明接口。
- 接口名稱(比如,此處的 IPerson),可以是任意合法的變量名稱。
- 聲明接口後,直接使用接口名稱作為變量的類型。
- 因為每一行隻有一個屬性類型,是以,屬性類型後沒有 ;(分号)。 interface(接口)和 type(類型别名)的對比:
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript - 相同點:都可以給對象指定類型。
-
不同點:
接口,隻能為對象指定類型。
類型别名,不僅可以為對象指定類型,實際上可以為任意類型指定别名。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
如果兩個接口之間有相同的屬性或方法,可以将公共的屬性或方法抽離出來,通過繼承來實作複用。 比如,這兩個接口都有 x、y 兩個屬性,重複寫兩次,可以,但很繁瑣。
更好的方式:
解釋:
- 使用 extends(繼承)關鍵字實作了接口 Point3D 繼承 Point2D。
- 繼承後,Point3D 就有了 Point2D 的所有屬性和方法(此時,Point3D 同時有 x、y、z 三個屬性)。
2.9 元組
場景:在地圖中,使用經緯度坐标來标記位置資訊。
可以使用數組來記錄坐标,那麼,該數組中隻有兩個元素,并且這兩個元素都是數值類型。
使用 number[] 的缺點:不嚴謹,因為該類型的數組中可以出現任意多個數字。
更好的方式:元組(Tuple)。
元組類型是另一種類型的數組,它确切地知道包含多少個元素,以及特定索引對應的類型。
解釋:
元組類型可以确切地标記出有多少個元素,以及每個元素的類型。
該示例中,元素有兩個元素,每個元素的類型都是 number。
2.10 類型推論
在 TS 中,某些沒有明确指出類型的地方,TS 的類型推論機制會幫助提供類型。
換句話說:由于類型推論的存在,這些地方,類型注解可以省略不寫!
發生類型推論的 2 種常見場景:1 聲明變量并初始化時 2 決定函數傳回值時。
注意:這兩種情況下,類型注解可以省略不寫!
推薦:能省略類型注解的地方就省略(偷懶,充分利用TS類型推論的能力,提升開發效率)。 技巧:如果不知道類型,可以通過滑鼠放在變量名稱上,利用 VSCode 的提示來檢視類型。
2.11 類型斷言
有時候你會比 TS 更加明确一個值的類型,此時,可以使用類型斷言來指定更具體的類型。
比如,
注意:getElementById 方法傳回值的類型是 HTMLElement,該類型隻包含所有标簽公共的屬性或方法,不包含 a 标簽特有的 href 等屬性。
是以,這個類型太寬泛(不具體),無法操作 href 等 a 标簽特有的屬性或方法。
解決方式:這種情況下就需要使用類型斷言指定更加具體的類型。
使用類型斷言:
解釋:
- 使用 as 關鍵字實作類型斷言。
- 關鍵字 as 後面的類型是一個更加具體的類型(HTMLAnchorElement 是 HTMLElement 的子類型)。
- 通過類型斷言,aLink 的類型變得更加具體,這樣就可以通路 a 标簽特有的屬性或方法了。
另一種文法,使用 <> 文法,這種文法形式不常用知道即可:
技巧:在浏覽器控制台,通過 console.dir() 列印 DOM 元素,在屬性清單的最後面,即可看到該元素的類型。
2.12 字面量類型
思考以下代碼,兩個變量的類型分别是什麼?
通過 TS 類型推論機制,可以得到答案:
變量 str1 的類型為:string。
變量 str2 的類型為:‘Hello TS’。
解釋:
str1 是一個變量(let),它的值可以是任意字元串,是以類型為:string。
str2 是一個常量(const),它的值不能變化隻能是 ‘Hello TS’,是以,它的類型為:‘Hello TS’。
注意:此處的 ‘Hello TS’,就是一個字面量類型。也就是說某個特定的字元串也可以作為 TS 中的類型。 除字元串外,任意的 JS 字面量(比如,對象、數字等)都可以作為類型使用。
使用模式:字面量類型配合聯合類型一起使用。 使用場景:用來表示一組明确的可選值清單。
比如,在貪吃蛇遊戲中,遊戲的方向的可選值隻能是上、下、左、右中的任意一個。
解釋:參數 direction 的值隻能是 up/down/left/right 中的任意一個。
優勢:相比于 string 類型,使用字面量類型更加精确、嚴謹。
2.13 枚舉
枚舉的功能類似于字面量類型+聯合類型組合的功能,也可以表示一組明确的可選值。
枚舉:定義一組命名常量。它描述一個值,該值可以是這些命名常量中的一個。
解釋:
- 使用 enum 關鍵字定義枚舉。
- 約定枚舉名稱、枚舉中的值以大寫字母開頭。
- 枚舉中的多個值之間通過 ,(逗号)分隔。
- 定義好枚舉後,直接使用枚舉名稱作為類型注解。
注意:形參 direction 的類型為枚舉 Direction,那麼,實參的值就應該是枚舉 Direction 成員的任意一個。
通路枚舉成員:
解釋:類似于 JS 中的對象,直接通過點(.)文法通路枚舉的成員。
問題:把枚舉成員作為了函數的實參,它的值是什麼呢?
解釋:通過将滑鼠移入 Direction.Up,可以看到枚舉成員 Up 的值為 0。
注意:枚舉成員是有值的,預設為:從 0 開始自增的數值。
我們把,枚舉成員的值為數字的枚舉,稱為:數字枚舉。 當然,也可以給枚舉中的成員初始化值。
字元串枚舉:枚舉成員的值是字元串。
注意:字元串枚舉沒有自增長行為,是以,字元串枚舉的每個成員必須有初始值。
枚舉是 TS 為數不多的非 JavaScript 類型級擴充(不僅僅是類型)的特性之一。
因為:其他類型僅僅被當做類型,而枚舉不僅用作類型,還提供值(枚舉成員都是有值的)。
也就是說,其他的類型會在編譯為 JS 代碼時自動移除。但是,枚舉類型會被編譯為 JS 代碼!
說明:枚舉與前面講到的字面量類型+聯合類型組合的功能類似,都用來表示一組明确的可選值清單。
一般情況下,推薦使用字面量類型+聯合類型組合的方式,因為相比枚舉,這種方式更加直覺、簡潔、高效。
2.14 any 類型
原則:不推薦使用 any!這會讓 TypeScript 變為 “AnyScript”(失去 TS 類型保護的優勢)。
因為當值的類型為 any 時,可以對該值進行任意操作,并且不會有代碼提示。
解釋:以上操作都不會有任何類型錯誤提示,即使可能存在錯誤!
盡可能的避免使用 any 類型,除非臨時使用 any 來“避免”書寫很長、很複雜的類型!
其他隐式具有 any 類型的情況:1 聲明變量不提供類型也不提供預設值 2 函數參數不加類型。 注意:因為不推薦使用 any,是以,這兩種情況下都應該提供類型!
2.15 typeof
衆所周知,JS 中提供了 typeof 操作符,用來在 JS 中擷取資料的類型。
實際上,TS 也提供了 typeof 操作符:可以在類型上下文中引用變量或屬性的類型(類型查詢)。
使用場景:根據已有變量的值,擷取該值的類型,來簡化類型書寫。
解釋:
- 使用 typeof 操作符來擷取變量 p 的類型,結果與第一種(對象字面量形式的類型)相同。
- typeof 出現在類型注解的位置(參數名稱的冒号後面)所處的環境就在類型上下文(差別于 JS 代碼)。
- 注意:typeof 隻能用來查詢變量或屬性的類型,無法查詢其他形式的類型(比如,函數調用的類型)。
3. TypeScript 進階類型
概述
TS 中的進階類型有很多,重點學習以下進階類型:
- class 類
- 類型相容性
- 交叉類型
- 泛型 和 keyof
- 索引簽名類型 和 索引查詢類型
- 映射類型
3.1 class 類
TypeScript 全面支援 ES2015 中引入的 class 關鍵字,并為其添加了類型注解和其他文法(比如,可見性修飾符等)
。
class 基本使用,如下:
解釋:
- 根據 TS 中的類型推論,可以知道 Person 類的執行個體對象 p 的類型是 Person。
- TS 中的 class,不僅提供了 class 的文法功能,也作為一種類型存在。
執行個體屬性初始化:
解釋:
- 聲明成員 age,類型為 number(沒有初始值)。
- 聲明成員 gender,并設定初始值,此時,可省略類型注解(TS 類型推論 為 string 類型)。
構造函數:
解釋:
- 成員初始化(比如,age: number)後,才可以通過 this.age 來通路執行個體成員。
- 需要為構造函數指定類型注解,否則會被隐式推斷為 any;構造函數不需要傳回值類型。
執行個體方法:
解釋:方法的類型注解(參數和傳回值)與函數用法相同。
類繼承的兩種方式:1 extends(繼承父類) 2 implements(實作接口)。 說明:JS 中隻有 extends,而 implements 是 TS 提供的。
extends
解釋:
- 通過 extends 關鍵字實作繼承。
- 子類 Dog 繼承父類 Animal,則 Dog 的執行個體對象 dog 就同時具有了父類 Animal 和 子類 Dog 的所有屬性和方法。
implements
解釋:
- 通過 implements 關鍵字讓 class 實作接口。
- Person 類實作接口 Singable 意味着,Person 類中必須提供 Singable 接口中指定的所有方法和屬性。
類成員可見性:可以使用 TS 來控制 class 的方法或屬性對于 class 外的代碼是否可見。
可見性修飾符包括:1 public(公有的) 2 protected(受保護的) 3 private(私有的)。
- public:表示公有的、公開的,公有成員可以被任何地方通路,預設可見性。 解釋:
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
- 在類屬性或方法前面添加 public 關鍵字,來修飾該屬性或方法是共有的。
- 因為 public 是預設可見性,是以,可以直接省略。
- protected:表示受保護的,僅對其聲明所在類和子類中(非執行個體對象)可見。
解釋:
- 在類屬性或方法前面添加 protected 關鍵字,來修飾該屬性或方法是受保護的。
- 在子類的方法内部可以通過 this 來通路父類中受保護的成員,但是,對執行個體不可見!
- private:表示私有的,隻在目前類中可見,對執行個體對象以及子類也是不可見的。 解釋:
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
- 在類屬性或方法前面添加 private 關鍵字,來修飾該屬性或方法是私有的。
- 私有的屬性或方法隻在目前類中可見,對子類和執行個體對象也都是不可見的!
除了可見性修飾符之外,還有一個常見修飾符就是:readonly(隻讀修飾符)。
readonly:表示隻讀,用來防止在構造函數之外對屬性進行指派。
解釋:
- 使用 readonly 關鍵字修飾該屬性是隻讀的,注意隻能修飾屬性不能修飾方法。
- 注意:屬性 age 後面的類型注解(比如,此處的 number)如果不加,則 age 的類型為 18 (字面量類型)。
- 接口或者 {} 表示的對象類型,也可以使用 readonly。
3.2 類型相容性
兩種類型系統:1 Structural Type System(結構化類型系統) 2 Nominal Type System(标明類型系統)。
TS 采用的是結構化類型系統,也叫做 duck typing(鴨子類型),類型檢查關注的是值所具有的形狀。
也就是說,在結構類型系統中,如果兩個對象具有相同的形狀,則認為它們屬于同一類型。
解釋:
- Point 和 Point2D 是兩個名稱不同的類。
- 變量 p 的類型被顯示标注為 Point 類型,但是,它的值卻是 Point2D 的執行個體,并且沒有類型錯誤。
- 因為 TS 是結構化類型系統,隻檢查 Point 和 Point2D 的結構是否相同(相同,都具有 x 和 y 兩個屬性,屬性類型也相同)。
- 但是,如果在 Nominal Type System 中(比如,C#、Java 等),它們是不同的類,類型無法相容。
注意:在結構化類型系統中,如果兩個對象具有相同的形狀,則認為它們屬于同一類型,這種說法并不準确。 更準确的說法:對于對象類型來說,y 的成員至少與 x 相同,則 x 相容 y(成員多的可以指派給少的)。
解釋:
- Point3D 的成員至少與 Point 相同,則 Point 相容 Point3D。
- 是以,成員多的 Point3D 可以指派給成員少的 Point。
除了 class 之外,TS 中的其他類型也存在互相相容的情況,包括:1 接口相容性 2 函數相容性 等。
接口之間的相容性,類似于 class。并且,class 和 interface 之間也可以相容。
除了 class 之外,TS 中的其他類型也存在互相相容的情況,包括:1 接口相容性 2 函數相容性 等。
- 接口之間的相容性,類似于 class。并且,class 和 interface 之間也可以相容。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
函數之間相容性比較複雜,需要考慮:1 參數個數 2 參數類型 3 傳回值類型。
- 參數個數,參數多的相容參數少的(或者說,參數少的可以指派給多的)。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript 解釋:TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
- 參數少的可以指派給參數多的,是以,f1 可以指派給 f2。
- 數組 forEach 方法的第一個參數是回調函數,該示例中類型為:(value: string, index: number, array: string[]) => void。
- 在 JS 中省略用不到的函數參數實際上是很常見的,這樣的使用方式,促成了 TS 中函數類型之間的相容性。
- 并且因為回調函數是有類型的,是以,TS 會自動推導出參數 item、index、array 的類型。
-
參數類型
相同位置的參數類型要相同(原始類型)或相容(對象類型)。
解釋:函數類型 F2 相容函數類型 F1,因為 F1 和 F2 的第一個參數類型相同。
相同位置的參數類型要相同或相容。
解釋:
- 注意,此處與前面講到的接口相容性沖突。
- 技巧:将對象拆開,把每個屬性看做一個個參數,則,參數少的(f2)可以指派給參數多的(f3)。
- 傳回值類型,隻關注傳回值類型本身即可:
解釋:
- 如果傳回值類型是原始類型,此時兩個類型要相同,比如,左側類型 F5 和 F6。
- 如果傳回值類型是對象類型,此時成員多的可以指派給成員少的,比如,右側類型 F7 和 F8。
3.3 交叉類型
交叉類型(&):功能類似于接口繼承(extends),用于組合多個類型為一個類型(常用于對象類型)。
比如,
解釋:使用交叉類型後,新的類型 PersonDetail 就同時具備了 Person 和 Contact 的所有屬性類型。
相當于,
交叉類型(&)和接口繼承(extends)的對比:
- 相同點:都可以實作對象類型的組合。
- 不同點:兩種方式實作類型組合時,對于同名屬性之間,處理類型沖突的方式不同。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript 說明:以上代碼,接口繼承會報錯(類型不相容);交叉類型沒有錯誤,可以簡單的了解為:TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
3.4 泛型
泛型是可以在保證類型安全前提下,讓函數等與多種類型一起工作,進而實作複用,常用于:函數、接口、class 中。 需求:建立一個 id 函數,傳入什麼資料就傳回該資料本身(也就是說,參數和傳回值類型相同)。
比如,id(10) 調用以上函數就會直接傳回 10 本身。但是,該函數隻接收數值類型,無法用于其他類型。
為了能讓函數能夠接受任意類型,可以将參數類型修改為 any。但是,這樣就失去了 TS 的類型保護,類型不安全。
泛型在保證類型安全(不丢失類型資訊)的同時,可以讓函數等與多種不同的類型一起工作,靈活可複用。
實際上,在 C#和 Java 等程式設計語言中,泛型都是用來實作可複用元件功能的主要工具之一。
建立泛型函數:
解釋:
- 文法:在函數名稱的後面添加 <>(尖括号),尖括号中添加類型變量,比如此處的 Type。
- 類型變量 Type,是一種特殊類型的變量,它處理類型而不是值。
- 該類型變量相當于一個類型容器,能夠捕獲使用者提供的類型(具體是什麼類型由使用者調用該函數時指定)。
- 因為 Type 是類型,是以可以将其作為函數參數和傳回值的類型,表示參數和傳回值具有相同的類型。
- 類型變量 Type,可以是任意合法的變量名稱。
調用泛型函數:
解釋:
- 文法:在函數名稱的後面添加 <>(尖括号),尖括号中指定具體的類型,比如,此處的 number。
- 當傳入類型 number 後,這個類型就會被函數聲明時指定的類型變量 Type 捕獲到。
- 此時,Type 的類型就是 number,是以,函數 id 參數和傳回值的類型也都是 number。
同樣,如果傳入類型 string,函數 id 參數和傳回值的類型就都是 string。
這樣,通過泛型就做到了讓 id 函數與多種不同的類型一起工作,實作了複用的同時保證了類型安全。
簡化調用泛型函數:
解釋:
- 在調用泛型函數時,可以省略 <類型> 來簡化泛型函數的調用。
- 此時,TS 内部會采用一種叫做類型參數推斷的機制,來根據傳入的實參自動推斷出類型變量 Type 的類型。
- 比如,傳入實參 10,TS 會自動推斷出變量 num 的類型 number,并作為 Type 的類型。
推薦:使用這種簡化的方式調用泛型函數,使代碼更短,更易于閱讀。
說明:當編譯器無法推斷類型或者推斷的類型不準确時,就需要顯式地傳入類型參數。
泛型限制:預設情況下,泛型函數的類型變量 Type 可以代表多個類型,這導緻無法通路任何屬性。
比如,id(‘a’) 調用函數時擷取參數的長度:
解釋:Type 可以代表任意類型,無法保證一定存在 length 屬性,比如 number 類型就沒有 length。
此時,就需要為泛型添加限制來收縮類型(縮窄類型取值範圍)。
添加泛型限制收縮類型,主要有以下兩種方式:1 指定更加具體的類型 2 添加限制。
- 指定更加具體的類型 比如,将類型修改為 Type[](Type 類型的數組),因為隻要是數組就一定存在 length 屬性,是以就可以通路了。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
添加泛型限制收縮類型,主要有以下兩種方式:1 指定更加具體的類型 2 添加限制。
2. 添加限制
解釋:
- 建立描述限制的接口 ILength,該接口要求提供 length 屬性。
- 通過 extends 關鍵字使用該接口,為泛型(類型變量)添加限制。
- 該限制表示:傳入的類型必須具有 length 屬性。
注意:傳入的實參(比如,數組)隻要有 length 屬性即可,這也符合前面講到的接口的類型相容性。
泛型的類型變量可以有多個,并且類型變量之間還可以限制(比如,第二個類型變量受第一個類型變量限制)。
比如,建立一個函數來擷取對象中屬性的值:
解釋:
-
添加了第二個類型變量 Key,兩個類型變量之間使用(,)逗号分隔。
keyof 關鍵字接收一個對象類型,生成其鍵名稱(可能是字元串或數字)的聯合類型。
- 本示例中 keyof Type 實際上擷取的是 person 對象所有鍵的聯合類型,也就是:‘name’ | ‘age’。
- 類型變量 Key 受 Type 限制,可以了解為:Key 隻能是 Type 所有鍵中的任意一個,或者說隻能通路對象中存在的屬性。
泛型接口:接口也可以配合泛型來使用,以增加其靈活性,增強其複用性。
解釋:
- 在接口名稱的後面添加 <類型變量>,那麼,這個接口就變成了泛型接口。
- 接口的類型變量,對接口中所有其他成員可見,也就是接口中所有成員都可以使用類型變量。
- 使用泛型接口時,需要顯式指定具體的類型(比如,此處的 IdFunc)。
- 此時,id 方法的參數和傳回值類型都是 number;ids 方法的傳回值類型是 number[]。
實際上,JS 中的數組在 TS 中就是一個泛型接口。
解釋:當我們在使用數組時,TS 會根據數組的不同類型,來自動将類型變量設定為相應的類型。
技巧:可以通過 Ctrl + 滑鼠左鍵(Mac:option + 滑鼠左鍵)來檢視具體的類型資訊。
泛型類:class 也可以配合泛型來使用。
比如,React 的 class 元件的基類 Component 就是泛型類,不同的元件有不同的 props 和 state。
解釋:React.Component 泛型類兩個類型變量,分别指定 props 和 state 類型。
建立泛型類:
解釋:
- 類似于泛型接口,在 class 名稱後面添加 <類型變量>,這個類就變成了泛型類。
- 此處的 add 方法,采用的是箭頭函數形式的類型書寫方式。 類似于泛型接口,在建立 class 執行個體時,在類名後面通過 <類型> 來指定明确的類型。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
泛型工具類型:TS 内置了一些常用的工具類型,來簡化 TS 中的一些常見操作。
說明:它們都是基于泛型實作的(泛型适用于多種類型,更加通用),并且是内置的,可以直接在代碼中使用。
這些工具類型有很多,主要學習以下幾個:
Partial<Type>
Readonly<Type>
Pick<Type, Keys>
Record<Keys, Type>
泛型工具類型 - Partial 用來構造(建立)一個類型,将 Type 的所有屬性設定為可選。
解釋:構造出來的新類型 PartialProps 結構和 Props 相同,但所有屬性都變為可選的。
泛型工具類型 -
Readonly<Type>
用來構造一個類型,将 Type 的所有屬性都設定為 readonly(隻讀)。
解釋:構造出來的新類型 ReadonlyProps 結構和 Props 相同,但所有屬性都變為隻讀的。
當我們想重新給 id 屬性指派時,就會報錯:無法配置設定到 “id” ,因為它是隻讀屬性。
泛型工具類型 - Pick<Type, Keys> 從 Type 中選擇一組屬性來構造新類型。
解釋:
- Pick 工具類型有兩個類型變量:1 表示選擇誰的屬性 2 表示選擇哪幾個屬性。
- 其中第二個類型變量,如果隻選擇一個則隻傳入該屬性名即可。
- 第二個類型變量傳入的屬性隻能是第一個類型變量中存在的屬性。
- 構造出來的新類型 PickProps,隻有 id 和 title 兩個屬性類型。
泛型工具類型 - Record<Keys,Type> 構造一個對象類型,屬性鍵為 Keys,屬性類型為 Type。
解釋:
- Record 工具類型有兩個類型變量:1 表示對象有哪些屬性 2 表示對象屬性的類型。
- 建構的新對象類型 RecordObj 表示:這個對象有三個屬性分别為a/b/c,屬性值的類型都是 string[]。
3.5 索引簽名類型
絕大多數情況下,我們都可以在使用對象前就确定對象的結構,并為對象添加準确的類型。
使用場景:當無法确定對象中有哪些屬性(或者說對象中可以出現任意多個屬性),此時,就用到索引簽名類型了。
解釋:
- 使用 [key: string] 來限制該接口中允許出現的屬性名稱。表示隻要是 string 類型的屬性名稱,都可以出現在對象中。
- 這樣,對象 obj 中就可以出現任意多個屬性(比如,a、b 等)。
- key 隻是一個占位符,可以換成任意合法的變量名稱。
- 隐藏的前置知識:JS 中對象({})的鍵是 string 類型的。
在 JS 中數組是一類特殊的對象,特殊在數組的鍵(索引)是數值類型。
并且,數組也可以出現任意多個元素。是以,在數組對應的泛型接口中,也用到了索引簽名類型。
解釋:
- MyArray 接口模拟原生的數組接口,并使用 [n: number] 來作為索引簽名類型。
- 該索引簽名類型表示:隻要是 number 類型的鍵(索引)都可以出現在數組中,或者說數組中可以有任意多個元素。
- 同時也符合數組索引是 number 類型這一前提。
3.6 映射類型
映射類型:基于舊類型建立新類型(對象類型),減少重複、提升開發效率。
比如,類型 PropKeys 有 x/y/z,另一個類型 Type1 中也有 x/y/z,并且 Type1 中 x/y/z 的類型相同:
這樣書寫沒錯,但 x/y/z 重複書寫了兩次。像這種情況,就可以使用映射類型來進行簡化。
解釋:
- 映射類型是基于索引簽名類型的,是以,該文法類似于索引簽名類型,也使用了 []。
- Key in PropKeys 表示 Key 可以是 PropKeys 聯合類型中的任意一個,類似于 forin(let k in obj)。
- 使用映射類型建立的新對象類型 Type2 和類型 Type1 結構完全相同。
- 注意:映射類型隻能在類型别名中使用,不能在接口中使用。
映射類型除了根據聯合類型建立新類型外,還可以根據對象類型來建立:
解釋:
- 首先,先執行 keyof Props 擷取到對象類型 Props 中所有鍵的聯合類型即,‘a’ | ‘b’ | ‘c’。
- 然後,Key in … 就表示 Key 可以是 Props 中所有的鍵名稱中的任意一個。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
實際上,前面講到的泛型工具類型(比如,
Partial<Type>
)都是基于映射類型實作的。 比如,
Partial<Type>
的實作:
解釋:
- keyof T 即 keyof Props 表示擷取 Props 的所有鍵,也就是:‘a’ | ‘b’ | ‘c’。
- 在 [] 後面添加 ?(問号),表示将這些屬性變為可選的,以此來實作 Partial 的功能。
- 冒号後面的 T[P] 表示擷取 T 中每個鍵對應的類型。比如,如果是 ‘a’ 則類型是 number;如果是 ‘b’ 則類型是 string。
- 最終,新類型 PartialProps 和舊類型 Props 結構完全相同,隻是讓所有類型都變為可選了。
剛剛用到的 T[P] 文法,在 TS 中叫做索引查詢(通路)類型。
作用:用來查詢屬性的類型。
解釋:
- Props[‘a’] 表示查詢類型 Props 中屬性 ‘a’ 對應的類型 number。是以,TypeA 的類型為 number。
- 注意:[] 中的屬性必須存在于被查詢類型中,否則就會報錯。
索引查詢類型的其他使用方式:同時查詢多個索引的類型
解釋:使用字元串字面量的聯合類型,擷取屬性 a 和 b 對應的類型,結果為: string | number。
解釋:使用 keyof 操作符擷取 Props 中所有鍵對應的類型,結果為: string | number | boolean。
4. TypeScript 類型聲明檔案
幾乎所有的 JavaScript 應用都會引入許多第三方庫來完成任務需求。
這些第三方庫不管是否是用 TS 編寫的,最終都要編譯成 JS 代碼,才能釋出給開發者使用。
我們知道是 TS 提供了類型,才有了代碼提示和類型保護等機制。
但在項目開發中使用第三方庫時,你會發現它們幾乎都有相應的 TS 類型,這些類型是怎麼來的呢?類型聲明檔案 類型聲明檔案:用來為已存在的 JS 庫提供類型資訊。
這樣在 TS 項目中使用這些庫時,就像用 TS 一樣,都會有代碼提示、類型保護等機制了。
- TS 的兩種檔案類型
- 類型聲明檔案的使用說明
4.1 TS 中的兩種檔案類型
TS 中有兩種檔案類型:1 .ts 檔案 2 .d.ts 檔案。
- .ts 檔案:
- 既包含類型資訊又可執行代碼。
- 可以被編譯為 .js 檔案,然後,執行代碼。
- 用途:編寫程式代碼的地方。
- .d.ts 檔案:
- 隻包含類型資訊的類型聲明檔案。
- 不會生成 .js 檔案,僅用于提供類型資訊。
- 用途:為 JS 提供類型資訊。
總結:.ts 是 implementation(代碼實作檔案);.d.ts 是 declaration(類型聲明檔案)。
如果要為 JS 庫提供類型資訊,要使用 .d.ts 檔案。
4.2 類型聲明檔案的使用說明
在使用 TS 開發項目時,類型聲明檔案的使用包括以下兩種方式:
- 使用已有的類型聲明檔案
- 建立自己的類型聲明檔案
學習順序:先會用(别人的)再會寫(自己的)。
1 内置類型聲明檔案
内置類型聲明檔案:TS 為 JS 運作時可用的所有标準化内置 API 都提供了聲明檔案。
比如,在使用數組時,數組所有方法都會有相應的代碼提示以及類型資訊:
實際上這都是 TS 提供的内置類型聲明檔案。
可以通過 Ctrl + 滑鼠左鍵(Mac:option + 滑鼠左鍵)來檢視内置類型聲明檔案内容。
比如,檢視 forEach 方法的類型聲明,在 VSCode 中會自動跳轉到 lib.es5.d.ts 類型聲明檔案中。
當然,像 window、document 等 BOM、DOM API 也都有相應的類型聲明(lib.dom.d.ts)。
2 第三方庫的類型聲明檔案。
目前,幾乎所有常用的第三方庫都有相應的類型聲明檔案。
第三方庫的類型聲明檔案有兩種存在形式:1 庫自帶類型聲明檔案 2 由 DefinitelyTyped 提供。
- 庫自帶類型聲明檔案:比如,axios。 解釋:這種情況下,正常導入該庫,TS 就會自動加載庫自己的類型聲明檔案,以提供該庫的類型聲明。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript - 由 DefinitelyTyped 提供。
DefinitelyTyped 是一個 github 倉庫,用來提供高品質 TypeScript 類型聲明。
可以通過 npm/yarn 來下載下傳該倉庫提供的 TS 類型聲明包,這些包的名稱格式為:@types/*。
比如,@types/react、@types/lodash 等。
說明:在實際項目開發時,如果你使用的第三方庫沒有自帶的聲明檔案,VSCode 會給出明确的提示。
解釋:當安裝 @types/* 類型聲明包後,TS 也會自動加載該類聲明包,以提供該庫的類型聲明。
補充:TS 官方文檔提供了一個頁面,可以來查詢 @types/* 庫。
建立自己的類型聲明檔案:1 項目内共享類型 2 為已有 JS 檔案提供類型聲明。
- 項目内共享類型:如果多個 .ts 檔案中都用到同一個類型,此時可以建立 .d.ts 檔案提供該類型,實作類型共享。
操作步驟:
- 建立 index.d.ts 類型聲明檔案。
- 建立需要共享的類型,并使用 export 導出(TS 中的類型也可以使用 import/export 實作子產品化功能)。
- 在需要使用共享類型的 .ts 檔案中,通過 import 導入即可(.d.ts 字尾導入時,直接省略)。
- 為已有 JS 檔案提供類型聲明:
- 在将 JS 項目遷移到 TS 項目時,為了讓已有的 .js 檔案有類型聲明。
- 成為庫作者,建立庫給其他人使用。
注意:類型聲明檔案的編寫與子產品化方式相關,不同的子產品化方式有不同的寫法。但由于曆史原因,JS 子產品化的發展經曆過多種變化(AMD、CommonJS、UMD、ESModule 等),而 TS 支援各種子產品化形式的類型聲明。這就導緻
,類型聲明檔案相關内容又多又雜。
示範:基于最新的 ESModule(import/export)來為已有 .js 檔案,建立類型聲明檔案。
開發環境準備:使用 webpack 搭建,通過 ts-loader 處理 .ts 檔案。
TS 項目中也可以使用 .js 檔案。
在導入 .js 檔案時,TS 會自動加載與 .js 同名的 .d.ts 檔案,以提供類型聲明。
declare 關鍵字:用于類型聲明,為其他地方(比如,.js 檔案)已存在的變量聲明類型,而不是建立一個新的變量。
- 對于 type、interface 等這些明确就是 TS 類型的(隻能在 TS 中使用的),可以省略 declare 關鍵字。
- 對于 let、function 等具有雙重含義(在 JS、TS 中都能用),應該使用 declare 關鍵字,明确指定此處用于類型聲明
5. 在 React 中使用 TypeScript
React 項目中使用 TS。包括以下内容:
- 使用 CRA 建立支援 TS 的項目
- TS 配置檔案 tsconfig.json
- React 中的常用類型
5.1 使用 CRA 建立支援 TS 的項目
React 腳手架工具 create-react-app(簡稱:CRA)預設支援 TypeScript。
建立支援 TS 的項目指令:npx create-react-app 項目名稱 --template typescript。
當看到以下提示時,表示支援 TS 的項目建立成功:
相對于非 TS 項目,目錄結構主要由以下三個變化:
- 項目根目錄中增加了 tsconfig.json 配置檔案:指定 TS 的編譯選項(比如,編譯時是否移除注釋)。
- React 元件的檔案擴充名變為:*.tsx。
- src 目錄中增加了 react-app-env.d.ts:React 項目預設的類型聲明檔案。
TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript TypeScript學習筆記1. TypeScript 初體驗2. TypeScript 常用類型3. TypeScript 進階類型4. TypeScript 類型聲明檔案5. 在 React 中使用 TypeScript
react-app-env.d.ts:React 項目預設的類型聲明檔案。
三斜線指令:指定依賴的其他類型聲明檔案,types 表示依賴的類型聲明檔案包的名稱。
解釋:告訴 TS 幫我加載 react-scripts 這個包提供的類型聲明。
react-scripts 的類型聲明檔案包含了兩部分類型:
- react、react-dom、node 的類型
- 圖檔、樣式等子產品的類型,以允許在代碼中導入圖檔、SVG 等檔案。
TS 會自動加載該 .d.ts 檔案,以提供類型聲明(通過修改 tsconfig.json 中的 include 配置來驗證)。
5.2 TS 配置檔案 tsconfig.json
tsconfig.json 指定:項目檔案和項目編譯所需的配置項。
注意:TS 的配置項非常多(100+),以 CRA 項目中的配置為例來學習,其他的配置項用到時查文檔即可。
- tsconfig.json 檔案所在目錄為項目根目錄(與 package.json 同級)。
- tsconfig.json 可以自動生成,指令:tsc --init。
除了在 tsconfig.json 檔案中使用編譯配置外,還可以通過指令行來使用。 使用示範:tsc hello.ts --target es6。
注意:
- tsc 後帶有輸入檔案時(比如,tsc hello.ts),将忽略 tsconfig.json 檔案。
- tsc 後不帶輸入檔案時(比如,tsc),才會啟用 tsconfig.json。
推薦使用:tsconfig.json 配置檔案。
5.3 React 中的常用類型
前提說明:現在,基于 class 元件來講解 React+TS 的使用(最新的 React Hooks,在後面講解)。 在不使用 TS 時,可以使用 prop-types 庫,為 React 元件提供類型檢查。
說明:TS 項目中,推薦使用 TypeScript 實作元件類型校驗(代替 PropTypes)。
不管是 React 還是 Vue,隻要是支援 TS 的庫,都提供了很多類型,來滿足該庫對類型的需求。
注意:
- React 項目是通過 @types/react、@types/react-dom 類型聲明包,來提供類型的。
- 這些包 CRA 已幫我們安裝好(react-app-env.d.ts),直接用即可。
React 是元件化開發模式,React 開發主要任務就是寫元件,兩種元件:1 函數元件 2 class 元件。
- 函數元件,主要包括以下内容:
- 元件的類型
- 元件的屬性(props)
- 元件屬性的預設值(defaultProps)
- 事件綁定和事件對象
函數元件的類型以及元件的屬性
實際上,還可以直接簡化為(完全按照函數在 TS 中的寫法):
函數元件屬性的預設值(defaultProps)
實際上,還可以直接簡化為(完全按照函數在 TS 中的寫法):
事件綁定和事件對象
再比如,文本框:
技巧:在 JSX 中寫事件處理程式(e => {}),然後,把滑鼠放在 e 上,利用 TS 的類型推論來檢視事件對象類型。
2. class 元件,主要包括以下内容:
- 元件的類型、屬性、事件
- 元件狀态(state)
class 元件的類型
class 元件的屬性和屬性預設值
class 元件狀态(state)和事件