iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
JavaScript

用 TypeScript 重新定義前端開發:30 天的實踐與思考系列 第 25

Day25:TypeScript 的高級型別 (Advanced Types)

  • 分享至 

  • xImage
  •  

今天要介紹 TypeScript 裡的高級型別,分別是條件型別 (Conditional Types)、映射型別 (Mapped Types)與推斷型別 (Infer Types)。

一、條件型別 (Conditional Types)

條件型別允許你根據條件來決定最終型別,它的語法類似於 JavaScript 的三元運算子 (condition ? trueType : falseType)。

1. 基本範例

條件型別的語法格式如下:

T extends U ? X : Y

以上這段程式碼是:如果 T 可以被 U 擴展(代表 TU 的子型別),那麼型別結果就是 X,否則是 Y。舉個例子:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

在這裡,IsString 型別會檢查 T 是否是 string。如果是,返回 true;如果不是,返回 false

2. 進階範例

假設我們想創建一個函式來處理不同的參數型別,例如如果參數是 string 就返回字串長度,如果是 number 就返回數字本身:

function processValue<T>(value: T): T extends string ? number : T {
  if (typeof value === 'string') {
    return value.length as T extends string ? number : T;
  }
  return value;
}

const result1 = processValue("hello"); // 返回 5
const result2 = processValue(123);     // 返回 123

在這個範例,會根據輸入的型別來決定返回值的型別:如果輸入是字串,則返回字串長度(數字);如果是數字,則直接返回數字。

二、映射型別 (Mapped Types)

映射型別允許基於現有型別來創建新的型別。這概念有點像是我們在物件上遍歷每個屬性並對它進行某種指定操作。

1. 基本範例

映射型別的語法如下:

{ [P in K]: T }

這表示將某個型別 K 的每個屬性都轉換成型別 T

舉例來說,如果我們有一個型別 Person,想要把它的所有屬性變為可選(也就是說屬性可以存在或不存在),就可以使用映射型別來達到目的:

type Person = {
  name: string;
  age: number;
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

type OptionalPerson = Optional<Person>;

// OptionalPerson 變為:{ name?: string; age?: number }

2. 進階範例

假設我們有一個 API 會回傳一個物件,物件內的每個屬性都是唯讀的,我們想要創建一個函式來把它們轉換成可修改的屬性:

type ReadonlyAPIResponse = {
  readonly id: number;
  readonly name: string;
};

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

type EditableAPIResponse = Mutable<ReadonlyAPIResponse>;

// EditableAPIResponse 變為:{ id: number; name: string; }

在這裡,通過使用 -readonly,我們將物件的所有屬性從唯讀變成可修改。

三、推斷型別 (Infer Types)

推斷型別是 TypeScript 中的一種高級功能,它允許我們在條件型別中使用 infer 關鍵字來推斷某個型別。

1. 基本範例

假設我們有一個函式會回傳一個 Promise,我們想推斷這個 Promise 內部的值的型別:

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result1 = UnwrapPromise<Promise<number>>;  // number
type Result2 = UnwrapPromise<string>;           // string

在這裡,我們使用 infer U 來推斷 Promise 裡面的型別。如果 TPromise,那麼我們就提取出 Promise 裡的型別 U;否則直接返回 T

2. 進階範例

假設我們有一個函式,它可以返回多種類型的 Promise,我們想寫一個工具型別來推斷它的返回型別:

function getData(): Promise<{ id: number; name: string }> {
  return Promise.resolve({ id: 1, name: 'John' });
}

type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : any;

type Data = ReturnTypeOf<typeof getData>;  // Promise<{ id: number; name: string }>

在這裡,我們使用 infer R 來推斷函式 getData 的返回型別。這種用法對於處理複雜型別系統中的函式非常有幫助。

四、小結

高級型別是 TypeScript 的一大特點,讓開發者可以編寫更加靈活、強大且類型安全的程式碼。最後統整今天介紹的三種高級型別:

  • 條件型別 (Conditional Types):允許根據條件決定最終的型別,例如根據參數型別的不同,返回不同的結果。
  • 映射型別 (Mapped Types):能夠基於現有型別來創建新的型別,常用來處理屬性的可選性、唯讀性等。
  • 推斷型別 (Infer Types):在條件型別中推斷型別,讓我們能夠動態地提取某個型別的部分訊息。

上一篇
Day24:使用 TypeScript 為異步操作與 Promise 添加型別加持
下一篇
Day26:使用 TypeScript 處理第三方庫中的型別定義
系列文
用 TypeScript 重新定義前端開發:30 天的實踐與思考30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言