本文 AI 產出,尚未審核

TypeScript 工具型別:Record<K, T>


簡介

在日常的前端開發或 Node.js 專案中,我們常需要把 (key)與 (value)之間的關係固定下來,讓程式碼在編譯階段就能捕捉到錯誤、保證資料結構的一致性。
Record<K, T> 正是 TypeScript 為此提供的 工具型別(Utility Type)之一,它可以在編譯時動態產生一個物件型別,讓每個指定的鍵都對應到相同的值型別 T

使用 Record 的好處包括:

  • 可讀性提升:一眼就能看出「這是一個以 X 為鍵、Y 為值的映射」
  • 自動補全:IDE 能根據型別提供鍵名與值型別的完整提示
  • 錯誤防護:錯誤的鍵或不符合型別的值會在編譯階段被捕捉

下面我們將從概念、語法、實作範例一路深入,最後說明常見陷阱與最佳實踐,幫助你在實務上有效運用 Record


核心概念

1. Record 的基本語法

type Record<K extends keyof any, T> = {
  [P in K]: T;
};
  • K 必須是 可作為鍵的型別string | number | symbol)的子集合。
  • T所有鍵所對應的值型別,可以是任意型別,甚至是另一個物件型別。

簡單來說,Record<'a' | 'b', number> 會被展開成:

{
  a: number;
  b: number;
}

2. 為什麼不直接寫介面(interface)?

如果鍵的集合是 固定且少量,手動寫介面當然沒問題;但當鍵是 來自變數、Enum、或是 API 回傳的資料 時,手寫會變得冗長且易出錯。
Record 讓我們可以 以型別層級的方式 把這些動態鍵映射到同一型別,保持程式碼的 DRY(Don't Repeat Yourself)原則。

3. RecordPartialRequired 的關係

Record 只負責 鍵和值的對應,不會改變屬性的必填/選填狀態。若需要把所有屬性變成可選或必填,可結合其他工具型別:

type OptionalRecord<K extends keyof any, T> = Partial<Record<K, T>>;
type RequiredRecord<K extends keyof any, T> = Required<Record<K, T>>;

程式碼範例

以下示範 5 個常見且實用的 Record 用法,並加入詳細註解說明。

範例 1:建立字典(Dictionary)

// 需求:一個以語言代碼為鍵、語系文字為值的字典
type LocaleDict = Record<'en' | 'zh-TW' | 'ja', string>;

const messages: LocaleDict = {
  en: 'Hello',
  'zh-TW': '哈囉',
  ja: 'こんにちは',
};

// 編譯錯誤示範(缺少鍵或值型別不符)
// const wrong: LocaleDict = { en: 'Hi' }; // ❌ Property 'zh-TW' is missing
// const wrong2: LocaleDict = { en: 123, 'zh-TW': '哈囉', ja: 'こんにちは' }; // ❌ Type 'number' is not assignable to type 'string'

重點Record 讓鍵的完整性在編譯期即被驗證,避免遺漏或拼寫錯誤。


範例 2:把 Enum 轉成映射表

enum Role {
  Admin = 'ADMIN',
  Editor = 'EDITOR',
  Viewer = 'VIEWER',
}

// 每個角色對應的權限說明
type RoleDescription = Record<Role, string>;

const roleDesc: RoleDescription = {
  [Role.Admin]: '擁有全部權限',
  [Role.Editor]: '可編輯內容',
  [Role.Viewer]: '只能檢視',
};

使用 Enum 作為 K,可確保所有列舉成員都被映射,且在新增 Enum 成員時會自動觸發型別錯誤提醒。


範例 3:從外部 API 動態生成 Record

假設我們從後端取得的欄位清單是一個字串陣列,想要產生一個「欄位名稱 → 欄位型別」的映射表:

// 假資料:從 API 取得的欄位名稱
const apiFields = ['id', 'name', 'age'] as const;

// 透過 typeof + typeof[] 取得字面量型別
type ApiField = typeof apiFields[number]; // 'id' | 'name' | 'age'

// 以 Record 生成欄位型別映射(全部為 string,實務上可改成更精細的型別)
type ApiRecord = Record<ApiField, string>;

const userRecord: ApiRecord = {
  id: 'U001',
  name: 'Alice',
  age: '30',
};

技巧:使用 as const 讓字串陣列成為 字面量型別,再配合 Record 產生動態且安全的物件型別。


範例 4:深層結構的 Record(巢狀映射)

有時候我們需要 鍵 → 另一個映射表 的結構,例如多語系內容:

type Translations = Record<'en' | 'zh-TW' | 'ja', Record<string, string>>;

const i18n: Translations = {
  en: { welcome: 'Welcome', logout: 'Log out' },
  'zh-TW': { welcome: '歡迎', logout: '登出' },
  ja: { welcome: 'ようこそ', logout: 'ログアウト' },
};

// 取得日文的「logout」文字
const logoutJa = i18n.ja.logout; // 'ログアウト'

透過 巢狀 Record,我們可以在同一型別裡定義多層次的映射關係,保持結構一致性。


範例 5:結合 Partial 實作「可選設定」

假設我們有一個函式接受設定物件,所有設定皆可選,但若提供則必須符合特定型別:

type ConfigKey = 'timeout' | 'retry' | 'verbose';
type ConfigValue = number | boolean;

type Config = Partial<Record<ConfigKey, ConfigValue>>;

function init(config: Config = {}) {
  const timeout = (config.timeout ?? 3000) as number;
  const retry = (config.retry ?? 2) as number;
  const verbose = (config.verbose ?? false) as boolean;

  // ... 其他初始化邏輯
}

// 正確使用
init({ timeout: 5000, verbose: true });

// 編譯錯誤示範(型別不符)
// init({ retry: 'three' }); // ❌ Type 'string' is not assignable to type 'ConfigValue'

關鍵Partial<Record<...>> 讓所有鍵變成可選,同時仍保留 值的型別檢查


常見陷阱與最佳實踐

陷阱 說明 解決方式
鍵的型別不符合 keyof any 使用自訂類別或物件作為 K 會報錯。 只使用 stringnumbersymbol 或其聯集;若需要物件屬性,可先提取其鍵型別 keyof typeof obj
忘記 as const 而失去字面量型別 直接使用普通陣列會被推斷為 string[],導致 Record<string, …> 而非預期的聯集。 在字面量陣列後加上 as const,或使用 const enum
值型別過於寬鬆 Record<string, any> 失去型別檢查的意義。 盡量使用具體型別,必要時結合泛型或條件型別限制。
**與 Partial/Required 混用產生不必要的 ` undefined`** Partial<Record<…>> 會把每個屬性變成 `T
巢狀 Record 的深層型別推斷過於複雜 多層 Record 可能導致編譯時間變長,IDE 補全卡頓。 若層級超過 2~3 層,考慮抽離成獨立型別或使用 interface 來提升可讀性。

最佳實踐小結

  1. 先定義鍵的字面量型別type Keys = 'a' | 'b'enum),再套用 Record
  2. 盡量避免使用 any,把值型別寫得具體一點。
  3. 結合其他工具型別PartialReadonlyPick)以滿足需求。
  4. 在大型專案中,將常用的 Record 型別抽成 typeinterface,保持一致性。
  5. 使用 as const 讓字面量陣列/物件保留精確型別,讓 Record 發揮最大威力。

實際應用場景

場景 為什麼適合使用 Record
表單驗證規則 把欄位名稱映射到驗證函式:Record<FormField, (value: any) => boolean>
多語系 UI 文案 語言代碼 → 翻譯字串的字典,保證每個語言都有對應文字。
權限管理 角色代碼 → 權限陣列或說明文字,確保所有角色都有設定。
API 回傳的固定欄位 例如:`Record<'id'
動態產生的設定檔 從外部 JSON 讀取鍵名列表,使用 as const + Record 生成安全的設定介面。

總結

Record<K, T> 是 TypeScript 提供的 高階工具型別,它讓我們能在編譯階段快速、精確地描述「鍵 → 同型別值」的映射關係。

  • 透過 字面量型別Enumas const,我們可以把動態或外部取得的鍵安全地套用到 Record
  • 結合 PartialRequiredReadonly 等其他工具型別,可彈性控制屬性的必填與可變性。
  • 在實務開發中,Record 常被用於 字典、權限表、多語系翻譯、設定檔 等場景,大幅提升程式碼的可讀性與錯誤防護。

掌握 Record 的使用方式後,你將能更自信地設計資料結構,讓 TypeScript 的型別系統真正成為開發的「安全網」。祝你在 TypeScript 的世界裡寫出更乾淨、更可靠的程式碼! 🚀