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

更新於 2024/03/27閱讀時間約 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
本文探討了複利效應的重要性,並藉由巴菲特的投資理念,說明如何選擇穩定產生正報酬的資產及長期持有的核心理念。透過定期定額的投資方式,不僅能減少情緒影響,還能持續參與全球股市的發展。此外,文中介紹了使用國泰 Cube App 的便利性及低手續費,幫助投資者簡化投資流程,達成長期穩定增長的財務目標。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
前言 create react app 是一個可以快速設定 react 專案的一個工具,在建立專案時已經把 babel,webpack 都已經預先封裝設置好,如果我們要修改 webpack alias 設定該如何設定呢 什麼是 alias alias 在 webpack 設定意義叫做,檔案路徑
Thumbnail
當開發大型Web應用時,TypeScript可以提供靜態類型檢查,幫助開發者捕捉潛在的錯誤。結合Gin,你可以建立強大的、類型安全的後端API。
Thumbnail
自己在剛開始進入前端領域時,很剛好遇上需要使用 TypeScript 的案子,一開始都是跟著前輩怎麼寫就怎麼寫,不太有其他餘力來思考「為什麼」會需要寫這門程式語言,直到自己後來使用了 TypeScript 完整開發了電商的購物流程,才慢慢理解到使用 TypeScript 的好處與優勢。
Thumbnail
Todo App是一個很好學習程式語言的專案開始,JayLin來帶大家手把手用TypeScript | React | TailwildCSS 來做一個小專案
Thumbnail
想要知道如何用最新技術來製作一個App嗎? 跟著JayLin用React | Redux Tool Kit | TypeScript | TailwildCSS 來製作一個Drawing App
Thumbnail
Typescript: It's not actually validating your types. - DEV Community 👩‍💻👨‍💻 有時他會讓你誤解: 我遇到一個相信 typescript 保證型別就是你說的那樣. 但我必須告訴你: Typescript 不是這樣做的.
Thumbnail
我們在蠻多文章中,都會使用「股價營收比」來計算股票的便宜價和昂貴價,這篇文章就分享給大家
Thumbnail
本文探討了複利效應的重要性,並藉由巴菲特的投資理念,說明如何選擇穩定產生正報酬的資產及長期持有的核心理念。透過定期定額的投資方式,不僅能減少情緒影響,還能持續參與全球股市的發展。此外,文中介紹了使用國泰 Cube App 的便利性及低手續費,幫助投資者簡化投資流程,達成長期穩定增長的財務目標。
Thumbnail
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
前言 create react app 是一個可以快速設定 react 專案的一個工具,在建立專案時已經把 babel,webpack 都已經預先封裝設置好,如果我們要修改 webpack alias 設定該如何設定呢 什麼是 alias alias 在 webpack 設定意義叫做,檔案路徑
Thumbnail
當開發大型Web應用時,TypeScript可以提供靜態類型檢查,幫助開發者捕捉潛在的錯誤。結合Gin,你可以建立強大的、類型安全的後端API。
Thumbnail
自己在剛開始進入前端領域時,很剛好遇上需要使用 TypeScript 的案子,一開始都是跟著前輩怎麼寫就怎麼寫,不太有其他餘力來思考「為什麼」會需要寫這門程式語言,直到自己後來使用了 TypeScript 完整開發了電商的購物流程,才慢慢理解到使用 TypeScript 的好處與優勢。
Thumbnail
Todo App是一個很好學習程式語言的專案開始,JayLin來帶大家手把手用TypeScript | React | TailwildCSS 來做一個小專案
Thumbnail
想要知道如何用最新技術來製作一個App嗎? 跟著JayLin用React | Redux Tool Kit | TypeScript | TailwildCSS 來製作一個Drawing App
Thumbnail
Typescript: It's not actually validating your types. - DEV Community 👩‍💻👨‍💻 有時他會讓你誤解: 我遇到一個相信 typescript 保證型別就是你說的那樣. 但我必須告訴你: Typescript 不是這樣做的.
Thumbnail
我們在蠻多文章中,都會使用「股價營收比」來計算股票的便宜價和昂貴價,這篇文章就分享給大家