本文 AI 產出,尚未審核

TypeScript 進階型別操作:typeof

簡介

在 TypeScript 中,typeof 不只是 JavaScript 的運算子,它同時是型別系統裡的關鍵工具。透過 typeof,我們可以在編譯階段直接取得 變數或屬性的型別,讓程式碼的可維護性與型別安全性大幅提升。特別是在大型專案或需要與外部 API 互動時,正確地「鏡射」實際值的型別,能避免因手動寫錯型別而產生的執行時錯誤。

本單元將深入探討 typeof 的用法、常見的陷阱以及在真實開發情境中的最佳實踐,讓讀者從 初學者 逐步晉升為 中級開發者,在日常開發中自然地運用這項技巧。


核心概念

1. typeof 在 TypeScript 中的角色

在 TypeScript 裡,typeof 會被 編譯器 解析,而不是在執行時求值。它會從 已宣告的值 中抽取型別,產生一個 型別別名,供後續使用。這與 JavaScript 中的 typeof(回傳字串)截然不同。

const version = "1.2.3";
// TypeScript 會把下面的型別推斷為 string
type Version = typeof version;   // -> string

重點typeof 只能拿已經有 (常數、變數、屬性)來產生型別,不能直接對抽象概念使用。

2. 基本用法:取得變數的型別

// ------------------- 範例 1 -------------------
const MAX_RETRY = 5;
type MaxRetry = typeof MAX_RETRY;   // number

// ------------------- 範例 2 -------------------
let userName = "Alice";
type UserName = typeof userName;   // string

// ------------------- 範例 3 -------------------
const config = {
  host: "localhost",
  port: 8080,
  secure: true,
};
type Config = typeof config;
/* Config 等價於:
{
  host: string;
  port: number;
  secure: boolean;
}
*/

透過上述範例,我們可以看到 typeof 直接把變數的型別抽出,再利用 type 關鍵字建立別名,讓其他地方可以重複使用。

3. 結合索引類型 (keyof) 取得屬性型別

// ------------------- 範例 4 -------------------
type ConfigKey = keyof typeof config;   // "host" | "port" | "secure"

type PortType = typeof config["port"];  // number

使用 keyof typeof,可以 動態產生屬性名稱的聯集型別;而 typeof obj["prop"] 則可直接抓出某個屬性的型別,對於 映射型別 (Mapped Types)條件型別 的寫法非常有幫助。

4. 用於函式返回型別推斷

// ------------------- 範例 5 -------------------
function createUser(id: number, name: string) {
  return { id, name };
}

type User = ReturnType<typeof createUser>;
/* User 等價於:
{
  id: number;
  name: string;
}
*/

ReturnType<T> 本身就是一個泛型型別工具,它會 利用 typeof 取得函式的型別,再推斷出返回值的型別。這樣的寫法可以避免手動維護返回物件的結構。

5. 透過 as consttypeof 保留字面量型別

// ------------------- 範例 6 -------------------
const STATUS = {
  SUCCESS: "success",
  FAIL: "fail",
} as const;

type Status = typeof STATUS[keyof typeof STATUS]; // "success" | "fail"

加上 as const 後,物件的每個屬性會被視為 字面量型別,而非寬鬆的 string。配合 typeof,可以快速得到 嚴格的枚舉型別,在 Redux、狀態機等場景非常實用。


常見陷阱與最佳實踐

陷阱 說明 解法 / 最佳實踐
使用 typeof 取得未初始化的變數 若變數在 declare 前未賦值,typeof 會得到 anyundefined 確保變數已被 賦予初始值,或使用 declare const 搭配手動型別註記。
typeof 不能直接作用於類型本身 typeof string 會報錯,因為 string 已是型別。 只對 使用 typeof,若想取得型別,直接使用 stringnumber 等關鍵字。
與聯集/交叉型別混用時失去字面量 typeof obj.prop 可能被寬化為基礎型別。 搭配 as const手動斷言 (as const) 保留字面量。
在命名空間或模組內使用 typeof 時的作用域問題 typeof 只能看到同一作用域內的值。 若需要跨檔案共享型別,先在模組中導出值,再在其他檔案使用 import + typeof
過度依賴 typeof 產生過於複雜的型別 會讓型別錯誤訊息變得難以閱讀。 適度抽象:將複雜型別抽成 type 別名,保持可讀性。

最佳實踐

  1. 以值為來源:盡量讓型別來源於實際的常量或物件,減少手寫型別的錯誤風險。
  2. 使用 as const 鎖定字面量,配合 typeof 產生嚴格的聯集型別
  3. 建立共享型別檔:將常用的 typeof 別名集中於 types.ts,方便全專案引用。
  4. 結合 ReturnTypeParameters 等內建工具,讓函式型別自動同步。
  5. 在測試或 API Mock 時,使用 typeof 直接從真實回傳值產生型別,確保測試與實作保持一致。

實際應用場景

1. API 回傳資料的型別同步

// api.ts
export async function fetchUser(id: number) {
  const response = await fetch(`/api/users/${id}`);
  return (await response.json()) as const;   // 取得字面量型別
}

// types.ts
import { fetchUser } from "./api";

type User = Awaited<ReturnType<typeof fetchUser>>;
/* User 會自動對應 API 真實回傳結構 */

這樣即使後端調整回傳欄位,只要 fetchUser 的實作更新,User 型別也會同步,減少前端手動修改的成本。

2. Redux / Zustand 狀態管理的 Action 型別

// actions.ts
export const ACTIONS = {
  ADD_TODO: "ADD_TODO",
  REMOVE_TODO: "REMOVE_TODO",
} as const;

export type ActionType = typeof ACTIONS[keyof typeof ACTIONS];
// ActionType = "ADD_TODO" | "REMOVE_TODO"

使用 typeof ACTIONS 搭配 as const,可以一次定義 字面量常數對應的型別,避免兩者不同步的問題。

3. 多語系 (i18n) 文本鍵值的型別保護

// i18n.ts
export const LANG = {
  en: { welcome: "Welcome", logout: "Logout" },
  zh: { welcome: "歡迎", logout: "登出" },
} as const;

export type LangKey = keyof typeof LANG["en"];   // "welcome" | "logout"

在組件中使用 LangKey 作為參數,編譯器會保證只傳入已定義的文字鍵,減少因拼寫錯誤導致的執行時缺字。


總結

typeofTypeScript 進階型別操作 中的基礎卻強大的工具。透過它,我們可以:

  • 自動抽取 常量、物件、函式的型別,減少手寫型別的工作量。
  • 結合 keyofas constReturnType 等特性,產生嚴謹且可維護的型別。
  • 避免型別與實際值不同步 的常見錯誤,提升程式碼的安全性與可讀性。

在日常開發—無論是 API 整合、狀態管理、還是多語系系統—善用 typeof 能讓型別系統「自動跟著」程式碼變化,讓開發者更專注於業務邏輯本身。希望本篇文章能幫助你在 TypeScript 的型別世界裡,更自信、更高效 地寫出可靠的程式碼。祝開發順利!