[TypeScript] 10 分鐘學會 TS 中必會的 5 種型別防禦 Type Guard

更新於 發佈於 閱讀時間約 10 分鐘
Type Guard

Type Guard


會希望寫這篇文章,是有鑑於在學習 TypeScript 後,很常遇到在參數可以輸入多個型別,但卻可能會使用到不同的方法或屬性時,總是會遇到各種困難。為了了解並避免未來遇到這種狀況時,變成一位 any 工程師,因此先寫下這篇文章以供參考。

本文大綱

    • 為什麼會需要型別防禦(Type Guard)?
    • 聯合型別的特性與用途與型別防禦的關聯。
    • 五種常見的型別防禦判斷方式。
    • 結論與延伸。

就讓我們開始來了解,TypeScript 中五種常見的型別防禦方式吧!



為什麼會需要型別防禦(Type Guard)?

在撰寫 TypeScript 的過程中,很常會遇到「我希望參數可以輸入不同的資料型別,但各自的資料卻有不同的方法」。

例如下面的例子:

  interface Car {
drive(): void;
stop(): void;
}

interface Airplane {
fly(): void;
stop(): void;
}

// 下面函式會報錯,之後也會說明如何修正
function getStarted(vehicle: Car | Airplane): Car | Airplane {
if (vehicle.drive) vehicle.drive;
else vehicle.fly;
}

上述的例子是,我們有 CarAirplane 兩個函式,我們各自希望他在 getStart 被呼叫時,可以啟動各自的方法。

那這和 Type Guard 有什麼關係?而之所以需要處理 Type Guard 的原因,就是因為我們的參數 vehicle 有兩種,因此需要特別進行判斷。

開始說明前,也簡單說明 TypeScript 的聯合型別是什麼。




聯合型別的特性與型別防禦(Type Guard)的關聯

聯合型別(Union Type)允許一個變數屬於多種型別之一,提供了靈活處理不同型別值的能力。例如,let myVar: string | number; 聲明了一個變數 myVar,它可以存儲字符串或數字型別的值。這在處理如使用者輸入、API 資料回傳等中特別有用,讓我們能夠在限縮變數的型別時,又保留了一些彈性。

但也因為參數的不確定,導致函式內部的方法也無法確定一定可以運作。大家多少有類似的經驗,例如使用 map 渲染一個 List,結果在輸入參數為其中一種,但相對應呼叫的物件卻沒有該方法時,就會讓程式意外出現 Error。

接下來,就讓我們來介紹常見的五種型別判斷,並說明使用時機為何。

  1. 使用型別斷言處理聯合型別
  2. 利用型別謂詞(Type Predicates)判定精確型別
  3. 使用 in 來判斷是否為特定屬性或方法
  4. 使用 typeof 簡化型別檢查
  5. 運用 instanceof 處理類別型別




1. 使用型別斷言處理聯合型別

每一個需要使用其屬性的地方,都需要使用斷言來確認其型別。

型別斷言是一種 TypeScript 語法,讓開發者告訴編譯器,用以確定變數的具體型別。例如,我們有一個變數 valuestring | number 型別。

如果 valuestring,就可以使用型別斷言的關鍵字 as,將 value 斷言為 string,例如:

let strValue = value as string;

當使用斷言 as 後,後續就可以安全地使用 string 的方法,例如 toUpperCase() 等。但如果在當下無法判斷是否一定為特定型別,就需要在每一處都寫上 as

實際案例:

// 除非使用斷言,不然就會報錯

function getStarted(vehicle: Car | Airplane): Car | Airplane {
if ((vehicle as Car).drive) {
(vehicle as Car).drive();
} else if ((vehicle as Airplane).fly) {
(vehicle as Airplane).fly();
}
}

因為上述的 vehicle 如果沒有使用斷言,TS 並不確定他一定會有該屬性。因此如果直接使用 vehicle.drive() 就會報錯。




2. 利用型別謂詞判定精確型別

型別謂詞是 TypeScript 中一個高級功能,它允許在函數中定義一個返回值為特定型別謂詞的表達式。這種方法非常適合於實現自定義 Type Guard。可以看到上述的方式,就算使用判斷式讓程式不會出錯,仍舊需要使用大量的斷言,這會導致程式碼難以閱讀。

透過 Type Guards 的型別謂詞(Type Predicates),可以有效優化程式碼的結構。要定義一個 Type Guard,其實只需要使用一個函式來處理即可。他需要返回一個型別謂詞,簡單來說就是返回一個 boolean,藉以確認是否屬於該屬性。

如何定義型別謂詞(Type Predicates)?

返回的 parameterName is Type 這個形式便是型別謂詞,且 parameterName 必須為參數之一。

關鍵在於 vehicle is Car 這一段型別謂詞透過 is 來確定其型別。若返回 true 代表 vehicle 是 Car 型別,TS 便會協助縮減型別;反之則為 Airplane

function isCar(vehicle: Car | Airplane): vehicle is Car {
return (vehicle as Car).drive !== undefined;
}


應用型別謂詞後的差異

透過 isCar 限縮型別,下面的例子便可以順利使用,而不需要使用斷言:

// 'drive' 和 'fly' 都不會報錯了
if (isCar(vehicle)) vehicle.drive();
else vehicle.fly();

需要特別注意,此處因為僅有 CarAirplane,因此才可以使用 else 直接指定為 Airplane 型別,不然如果有三種類型,就一樣需要 else if 來判斷類型,不然 TS 一樣會不知道是哪一種。




3. 使用 in 來判斷是否為特定屬性或方法

若要簡化並確認特定物件中,是否有所屬的屬性或方法,可以使用 in。其使用方法為 n in xn樣板字面值(Template Literal)字串(String),而 x 則為聯合類型(Union Type)

若返回 true,代表其擁有一個必須或可選且存在的特定屬性或方法;若為 false 則為一個可選屬性不存在n 屬性。


實際程式碼

if ("drive" in vehicle) vehicle.drive();
else vehicle.fly();

透過 JavaScript 內建的 in 方法,就能夠判斷該物件裡是否有 drive 屬性,進而提升程式碼的可閱讀性,也避免了必須使用斷言的問題。




4. 使用 typeof 簡化型別檢查

typeof 運算子是 JavaScript 中的一個原生功能,TypeScript 擴展了其用途作為一種簡單的 Type Guard。如果我們要判斷原始型別如:

  • string
  • number
  • Symbol
  • boolean

以上四種類型,可以直接使用 typeof 來進行判斷。且 Type Guard 只會判別以上四種類型,且只有以下兩種形式:

  • typeof T === "number"
  • typeof T !== "number"

當然,我們還是可以和其他的字串比較,但 Type Guard 不會視為判斷基準。結合上面的型別謂詞用法,可以使用如下的形式:

function isString(test: any): test is string {
return typeof test === "string";
}




5. 運用 instanceof 處理類別型別

instanceof 運算子是另一種原生 JavaScript 功能,用於檢查一個對象是否為特定類別或其子類別的實例。在 TypeScript 中,它可以用作 Type Guard,特別是當處理某些類別(class)的實例,但在前端開發的領域因為 class 用的少,因此相較之下也比較少應用到。

class Animal {}
class Dog extends Animal {
bark: () => console.log('汪!');
}
class Cat extends Animal {
meow: () => console.log('喵!');
}

// 透過 instanceof 判斷使用
function makeSound(animal: Animal) {
if (animal instanceof Dog) {
animal.bark();
} else if (animal instanceof Cat) {
animal.meow();
}
}




實踐與應用

透過 Type Guard 的使用,能夠讓 TypeScript 更能夠判斷型別,進而提升程式碼的可維護性與安全性。且另一個優勢,是在撰寫型別判斷時,也能夠主動意識到輸入的型別可能的 Edge Case,進而保護程式碼。

未來因為工作中會需要大量使用 TypeScript,因此希望透過整理文章,讓自己的概念更加清晰。如果有任何建議或想法,都歡迎留言或來信補充!


參考資料 Reference

此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
本文將深入探討鏈表的核心概念,使用 JavaScript 來說明如何實現和操作鏈表(Linked List),包括 append、prepend、remove、find 和 reverse 等五大方法。
「px」,即像素,是最基本的單位,它常被用於指定字體大小、邊框粗細等。「em」和「rem」通常用於調整相對大小,「em」在子元素中的適用,而「rem」則以根元素為參考。另一方面,「vh」和「vw」分別代表視窗的高度和寬度百分比,特別適合實現響應式設計。「vmin」和「vmax」則根據視窗的最小或最大
在 React 測試生態系統中,React Testing Library 成為了方便好用的選擇,因其強調測試應該關注於使用者的操作與觀察元件行為,而不是測試細節實現。
在軟體領域中,"Thunk" 是一個常用的術語,它指的是一種用於延遲計算,或將運算延後執行的程式碼片段。它通常用於函數式編程,或編譯器的設計中。Redux 透過 createAsyncThunk 實作了該非同步/異步操作,並提供數個 API 協助我們使用 Redux。
React 表單驗證是一種技術與使用者體驗的設計,讓使用者能夠即時檢查輸入的資料並修正,提升使用者的使用體驗,並確保資料的正確性。
useContext 是一種 React hook,讓我們能夠直接取用其他元件的 Context,而無須層層傳遞 props,進而使程式碼簡潔易讀。
本文將深入探討鏈表的核心概念,使用 JavaScript 來說明如何實現和操作鏈表(Linked List),包括 append、prepend、remove、find 和 reverse 等五大方法。
「px」,即像素,是最基本的單位,它常被用於指定字體大小、邊框粗細等。「em」和「rem」通常用於調整相對大小,「em」在子元素中的適用,而「rem」則以根元素為參考。另一方面,「vh」和「vw」分別代表視窗的高度和寬度百分比,特別適合實現響應式設計。「vmin」和「vmax」則根據視窗的最小或最大
在 React 測試生態系統中,React Testing Library 成為了方便好用的選擇,因其強調測試應該關注於使用者的操作與觀察元件行為,而不是測試細節實現。
在軟體領域中,"Thunk" 是一個常用的術語,它指的是一種用於延遲計算,或將運算延後執行的程式碼片段。它通常用於函數式編程,或編譯器的設計中。Redux 透過 createAsyncThunk 實作了該非同步/異步操作,並提供數個 API 協助我們使用 Redux。
React 表單驗證是一種技術與使用者體驗的設計,讓使用者能夠即時檢查輸入的資料並修正,提升使用者的使用體驗,並確保資料的正確性。
useContext 是一種 React hook,讓我們能夠直接取用其他元件的 Context,而無須層層傳遞 props,進而使程式碼簡潔易讀。
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
此章節旨在介紹TypeScript中的運算符,包括算數運算子、比較運算子、賦值運算子、位元運算子,以及他們的優先等級。每種運算子都以清晰的解釋和代碼範例進行詳細說明,幫助讀者理解並有效地在自己的程式碼中使用。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
此章節旨在介紹TypeScript中的運算符,包括算數運算子、比較運算子、賦值運算子、位元運算子,以及他們的優先等級。每種運算子都以清晰的解釋和代碼範例進行詳細說明,幫助讀者理解並有效地在自己的程式碼中使用。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。