iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0

1) 引言:為什麼需要映射型別?

假設我們有一個型別:

ts
CopyEdit
type User = {
  id: string;
  name: string;
  email: string;
};

如果今天要讓所有屬性變成 可選(optional),我們可能會:

ts
CopyEdit
type PartialUser = {
  id?: string;
  name?: string;
  email?: string;
};

但這樣一個一個改超累,而且很不 DRY(Don't Repeat Yourself)。

映射型別(Mapped Types) 就是為了「批量改造型別」而生的,

它可以讓我們用迴圈的概念一次處理所有屬性。


2) 映射型別的基礎語法

ts
CopyEdit
type MyMapped<T> = {
  [K in keyof T]: T[K];
};

這裡:

  • keyof T:取得型別 T 的所有鍵名(union 型別)
  • K in keyof T:遍歷每一個鍵
  • T[K]:取出該鍵的型別

範例:

ts
CopyEdit
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};


3) TypeScript 內建的映射型別

TS 內建了幾個常用映射型別:

  • Partial<T>:所有屬性變成可選
  • Required<T>:所有屬性變成必填
  • Readonly<T>:所有屬性變成唯讀
  • Pick<T, K>:挑選部分屬性
  • Omit<T, K>:刪除部分屬性
  • Record<K, T>:建立一個以 K 為鍵、T 為值的型別

範例:

ts
CopyEdit
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type IdNameOnly = Pick<User, "id" | "name">;


4) 搭配條件型別改造屬性型別

我們可以在映射型別裡用條件型別(Day 24 學過的技巧):

ts
CopyEdit
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

也可以根據屬性型別做判斷:

ts
CopyEdit
type NullableOnlyString<T> = {
  [K in keyof T]: T[K] extends string ? string | null : T[K];
};


5) 鍵名重新命名(Key Remapping)

TS 4.1 之後,映射型別支援 as 關鍵字來重新命名鍵名:

ts
CopyEdit
type RenameKeys<T, M extends Record<string, string>> = {
  [K in keyof T as K extends keyof M ? M[K] : K]: T[K];
};

type UserRenamed = RenameKeys<User, { id: "userId"; name: "fullName" }>;
// { userId: string; fullName: string; email: string }


6) 實戰應用 1:API DTO(Data Transfer Object)轉換

後端回傳:

ts
CopyEdit
type UserApiResponse = {
  id: string;
  name: string;
  email: string;
  created_at: string;
};

前端想要駝峰式:

ts
CopyEdit
type SnakeToCamel<T> = {
  [K in keyof T as K extends `${infer First}_${infer Rest}`
    ? `${First}${Capitalize<Rest>}`
    : K]: T[K];
};

type UserCamel = SnakeToCamel<UserApiResponse>;


7) 實戰應用 2:表單型別生成器

假設我們想從資料型別生成表單設定:

ts
CopyEdit
type FieldConfig<T> = {
  [K in keyof T]: {
    label: string;
    value: T[K];
    required: boolean;
  };
};

type UserForm = FieldConfig<User>;

這樣每個欄位的 value 型別會對應資料型別,非常安全。


8) 實戰應用 3:部分欄位可選

ts
CopyEdit
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type UserWithOptionalEmail = Optional<User, "email">;


9) 常見錯誤與陷阱

錯誤 1:忘了使用 keyof T

ts
CopyEdit
type Wrong<T> = {
  [K in T]: T[K]; // ❌ T 不是物件鍵
};

錯誤 2:忽略 as 可以重新命名鍵

  • 導致只能改值型別,不能改鍵名

錯誤 3:過度映射

  • 如果只是少數欄位變動,Pick / Omit 搭配手動定義會更簡潔

10) 心法

  1. 映射型別是「批量修改」的工具
    • 適合大範圍結構調整
  2. 結合條件型別與 Key Remapping
    • 可以做欄位篩選、重命名、型別轉換等複雜操作
  3. 和內建工具型別搭配使用
    • 常見需求 TS 幾乎都有內建解法

上一篇
Day 24|條件型別:讓型別也能寫 if/else
系列文
我與型別的 30 天約定:TypeScript 入坑實錄25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言