本文 AI 產出,尚未審核

TypeScript 進階型別操作:Indexed Access Type (T[K])


簡介

在日常開發中,我們常會遇到「從一個物件型別中取出某個屬性的型別」的需求。傳統的做法是手動寫出對應的型別,或是使用 any 失去型別安全。Indexed Access Type(索引存取型別)提供了一種直接、可維護的方式:T[K] 讓我們可以在編譯期就取得 T 中屬性 K 的型別。

這個特性不僅提升了程式碼的 可讀性可重用性,同時也讓 TypeScript 的型別系統更具表達力。無論是建立 API 回傳型別、實作泛型工具函式,或是打造大型專案的型別基礎建設,T[K] 都是不可或缺的利器。


核心概念

1. 基本語法與運作原理

T[K] 的語法結構如下:

type PropertyType = T[K];
  • T 必須是 物件型別(或類別型別)。
  • K 必須是 T 中的 (key),可以是字串、數字或 keyof T
  • 結果 PropertyTypeT 中屬性 K型別

範例:取得 User 介面的 name 型別

interface User {
  id: number;
  name: string;
  isAdmin: boolean;
}

type NameType = User["name"]; // string

2. 與 keyof 的結合使用

keyof T 會產生 T 所有鍵的聯集型別。將它與 T[K] 結合,可以一次取得 所有屬性型別的聯集

type AllValueTypes<T> = T[keyof T];

這在需要 遍歷 物件所有屬性時非常有用。

3. 索引存取的分布式條件型別(Distributive Conditional Types)

在泛型條件型別中使用 T[K],會自動分布到每個聯合成員上,讓我們能寫出更彈性的工具型別。例如:

type PropIfString<T, K extends keyof T> = T[K] extends string ? K : never;

此型別會挑出 T型別為 string 的屬性鍵。

4. 讀寫限制:readonly 與可選屬性

T[K] 只會返回屬性的 值型別,不會保留 readonly?(可選)修飾子。若需要保留這些資訊,必須搭配其他工具型別(如 PickPartial)使用。


程式碼範例

以下提供 5 個實務範例,展示 T[K] 在不同情境下的應用。

範例 1:從 API 回傳型別中抽取特定欄位

interface ApiResponse {
  status: number;
  data: {
    userId: string;
    token: string;
    expires: Date;
  };
  error?: string;
}

// 只想要取得 data 裡面的型別
type DataType = ApiResponse["data"]; // { userId: string; token: string; expires: Date; }

// 再抽出 token 的型別
type TokenType = ApiResponse["data"]["token"]; // string

這樣寫可以避免手動複製 data 結構,當 ApiResponse 改變時型別會自動同步。

範例 2:建立「屬性值」的聯集型別

interface Config {
  host: string;
  port: number;
  useSsl: boolean;
}

// 所有屬性的型別聯集
type ConfigValue = Config[keyof Config]; // string | number | boolean

在需要 驗證映射 任意屬性值時,ConfigValue 可直接作為參數型別。

範例 3:從物件取出「字串屬性」的鍵

type StringKeys<T> = {
  [K in keyof T]: T[K] extends string ? K : never
}[keyof T];

interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
}

// 只留下 string 型別的鍵
type ProductStringKeys = StringKeys<Product>; // "name" | "description"

這種技巧常用於 表單生成器,只挑出需要文字輸入的欄位。

範例 4:使用 T[K] 建立「映射」函式

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice", active: true };
const name = pluck(user, "name"); // name 的型別為 string
const active = pluck(user, "active"); // active 的型別為 boolean

pluck 函式的返回型別直接由 T[K] 推斷,使用者在呼叫時會得到正確的型別提示。

範例 5:搭配條件型別建立「必填屬性」的子型別

type RequiredProps<T> = {
  [K in keyof T]-?: T[K];
};

type UserPartial = {
  id?: number;
  name?: string;
  email?: string;
};

// 變成全部必填
type UserRequired = RequiredProps<UserPartial>;
// { id: number; name: string; email: string; }

雖然此例主要示範 -?(移除可選),但在實作時常會結合 T[K] 取得每個屬性的原始型別,以避免遺失資訊。


常見陷阱與最佳實踐

陷阱 說明 解決方式
使用不存在的鍵 T["notExist"] 會直接報錯。 使用 K extends keyof T 限制泛型,或先 keyof T 取得合法鍵集合。
readonly/? 失去 T[K] 只回傳值型別,readonly? 會被忽略。 若需要保留修飾子,搭配 Pick<T, K>Partial<T> 等工具型別。
聯合類型的分布行為 在條件型別中使用 T[K] 時會自動分布,可能產生意外的 never 明確使用 [K] 包裹或 Extract<...> 來控制分布。
索引類型的遞迴 直接 type DeepValue<T> = T[keyof T] 會在深層物件上失去結構。 需要遞迴條件型別來保留深層結構,例如 type DeepPick<T, K extends keyof T> = { [P in K]: DeepPick<T[P], keyof T[P]> }.

最佳實踐

  1. 限制鍵的範圍

    function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { ... }
    

    這樣可以保證只能傳入合法鍵,避免編譯錯誤。

  2. 結合 as const
    若想要把字面量陣列的元素當作鍵使用,記得加上 as const,讓 TypeScript 推斷為字面量型別:

    const fields = ["id", "name"] as const;
    type Field = typeof fields[number]; // "id" | "name"
    
  3. 使用 keyof 產生聯合鍵
    在需要「遍歷」所有屬性時,keyof T 搭配映射型別是最簡潔的寫法。

  4. 保持型別同步
    當原始介面變更時,依賴 T[K] 的型別會自動更新,減少手動維護的成本。


實際應用場景

  1. API 客戶端 SDK
    透過 Response["data"] 直接抽取資料型別,讓呼叫端只關注實際 payload,減少重複宣告。

  2. 表單生成器
    使用 StringKeys<T>NumberKeys<T> 只挑出需要的欄位,動態產生 UI,且型別安全。

  3. 資料映射函式
    pluckmapValues 等通用工具函式的返回型別皆可由 T[K] 推斷,提升函式的可重用性。

  4. 國際化 (i18n) 文字資源
    定義 type LocaleKey = keyof typeof zhTW; 再用 LocaleKey 取值,保證存取的 key 必定存在。

  5. Redux / Zustand 狀態管理
    從全域狀態型別中抽取子狀態(State["user"]),在 selector 中保持型別一致。


總結

  • Indexed Access Type (T[K]) 讓我們能在編譯期直接取得物件屬性的型別,提升型別安全與維護性。
  • 透過 keyof映射型別條件型別 等組合,可實作出高度彈性的工具型別,如挑選字串屬性鍵、產生值型別聯集等。
  • 在實務開發中,T[K] 常見於 API SDK、表單生成、狀態管理等情境,對大型專案的型別一致性尤其重要。
  • 注意避免鍵不存在、修飾子遺失與分布式條件型別的意外行為,遵守 限制鍵範圍結合 as const 等最佳實踐,可讓程式碼更穩健。

掌握了 T[K] 之後,你就能以更精簡、可讀的方式描述複雜資料結構,讓 TypeScript 的型別系統真正發揮威力。祝你在 TypeScript 的進階型別世界裡玩得開心!