本文 AI 產出,尚未審核

TypeScript 基本型別 — 字面值型別(Literal Types)


簡介

在 JavaScript 的世界裡,變數的型別是「動態」的,任何值都可以隨時被指派給任何變數。TypeScript 為了在編譯階段捕捉錯誤,引入了靜態型別系統,而 字面值型別(Literal Types) 則是其中一個非常實用且直觀的特性。
字面值型別讓我們可以把「具體的值」本身當作型別來使用,例如只能是 'success'、0 或 true 的變數。這樣的限制不僅讓程式碼的意圖更明確,也能在開發工具中得到更好的自動完成與錯誤提示。

對於初學者而言,字面值型別看起來像是小小的語法糖,但在實務開發中,它常與 聯合型別(Union Types)、映射型別(Mapped Types)、條件型別(Conditional Types) 搭配,打造出高度可讀且安全的 API 設計。掌握這個概念,能讓你在撰寫 UI 狀態管理、設定檔結構、甚至是函式參數驗證時,減少大量的執行期錯誤。


核心概念

1. 什麼是字面值型別?

字面值型別指的是 直接以具體值(字串、數字、布林)作為型別。在 TypeScript 中,字串 'admin'、數字 42、布林 false 都可以被視為型別本身。

let role: 'admin' | 'user' | 'guest';
role = 'admin';   // ✅ 合法
role = 'manager'; // ❌ 編譯錯誤:型別 '"manager"' 無法指派給型別 '"admin" | "user" | "guest"'

以上例子中,role 只能接受三個特定的字串字面值,任意其他字串都會在編譯期被拒絕。

2. 為什麼要使用字面值型別?

  • 提升可讀性:程式碼一眼就能看出變數能接受的值範圍。
  • 防止錯字:如果使用一般字串型別,'adminn' 這類拼寫錯誤不會被抓到;字面值型別則會直接報錯。
  • 配合 IDE 提示:大多數編輯器會根據字面值型別提供自動完成,減少手打錯誤。

3. 與聯合型別的結合

字面值型別最常與 聯合型別 結合,形成「限定集合」的概念。這也是實作 枚舉(Enum) 的另一種寫法。

type Direction = 'up' | 'down' | 'left' | 'right';

function move(dir: Direction) {
  console.log(`向 ${dir} 移動`);
}

move('up');   // ✅
move('jump'); // ❌ 編譯錯誤

4. 數字與布林的字面值型別

雖然字串最常見,數字與布林同樣可以作為字面值型別,特別適合「狀態碼」或「開關」的情境。

type HttpStatus = 200 | 201 | 400 | 404 | 500;

function isSuccess(code: HttpStatus): boolean {
  return code === 200 || code === 201;
}

isSuccess(200); // ✅
isSuccess(403); // ❌ 編譯錯誤

5. 透過 as const 產生字面值型別

在寫常量物件或陣列時,as const 可以把所有屬性自動推導為字面值型別,省去手動寫聯合型別的麻煩。

const COLORS = ['red', 'green', 'blue'] as const;
// COLORS 的型別變成 readonly ['red', 'green', 'blue']

type Color = typeof COLORS[number]; // 'red' | 'green' | 'blue'

function setBackground(c: Color) {
  console.log(`設定背景顏色為 ${c}`);
}

6. 字面值型別與函式重載

利用字面值型別,我們可以寫出更精確的函式重載,讓不同的參數組合回傳不同型別。

function format(value: number, format: 'currency'): string;
function format(value: number, format: 'percentage'): string;
function format(value: number, format: 'raw'): number;
function format(value: number, format: 'currency' | 'percentage' | 'raw'): string | number {
  if (format === 'currency') return `$${value.toFixed(2)}`;
  if (format === 'percentage') return `${(value * 100).toFixed(1)}%`;
  return value; // raw
}

format(1234, 'currency');   // string
format(0.85, 'percentage'); // string
format(42, 'raw');          // number

常見陷阱與最佳實踐

陷阱 可能的問題 解決方式
忘記加 as const 常量陣列會被推導為 string[],失去字面值限制 在宣告時使用 as const,或手動寫聯合型別
過度使用字面值型別 型別過於嚴格,導致需要大量型別斷言 只在 公共 API、狀態機 或 配置檔 中使用;內部實作可放寬
字面值型別與 enum 混用 兩者語意不同,可能產生重複定義 若需要 雙向映射(值 ↔︎ 名稱)使用 enum;若只需 限定集合 使用字面值型別
字面值型別與 any 互相污染 把 any 賦值給字面值型別會失去檢查 盡量避免 any,使用 unknown + 類型守衛來保留安全性
在泛型中忘記約束 泛型參數被推導為寬鬆型別,失去字面值優勢 使用 extends 來限制泛型,例如 `T extends 'on'

最佳實踐:

  1. 把字面值型別放在最外層:讓 API 使用者一眼就能看到允許的值。
  2. 結合 as const:對於常量資料,讓 TypeScript 自動推導字面值型別,減少冗長。
  3. 使用 type 而非 interface:字面值型別本質上是 type alias,使用 type 更直觀。
  4. 在函式簽名中明確列出:尤其是事件處理、狀態切換等函式,避免使用寬泛的 string。
  5. 搭配 lint 規則:如 @typescript-eslint/prefer-literal-enum-member,確保在需要時使用字面值而非 enum。

實際應用場景

1. UI 元件的狀態管理

type ButtonState = 'idle' | 'loading' | 'success' | 'error';

interface ButtonProps {
  state: ButtonState;
  onClick: () => void;
}

function MyButton({ state, onClick }: ButtonProps) {
  const label = {
    idle: '送出',
    loading: '載入中...',
    success: '完成',
    error: '重試',
  }[state];

  return <button disabled={state === 'loading'} onClick={onClick}>{label}</button>;
}

使用字面值型別保證 state 只能是四個預先定義的值,避免 UI 進入未知狀態。

2. API 請求的回傳型別

type ApiResult = 
  | { status: 'ok'; data: any }
  | { status: 'error'; errorCode: 400 | 401 | 403 | 500; message: string };

function handleResult(r: ApiResult) {
  if (r.status === 'ok') {
    console.log('成功取得資料', r.data);
  } else {
    console.error(`錯誤 (${r.errorCode}):${r.message}`);
  }
}

在錯誤情況下,errorCode 被限制為特定的 HTTP 錯誤碼,減少錯誤處理時的分支判斷。

3. 設定檔的嚴格驗證

const LOG_LEVELS = ['debug', 'info', 'warn', 'error'] as const;
type LogLevel = typeof LOG_LEVELS[number];

interface LoggerConfig {
  level: LogLevel;
  output: 'console' | 'file';
}

function initLogger(cfg: LoggerConfig) {
  // 只會接受合法的 level 與 output
}

透過 as const,LogLevel 自動變成 'debug' | 'info' | 'warn' | 'error',讓設定檔在寫入時即被 TypeScript 抓錯。

4. Redux / Zustand 等狀態容器的 Action Type

type CounterAction = 
  | { type: 'increment'; amount: number }
  | { type: 'decrement'; amount: number }
  | { type: 'reset' };

function reducer(state: number, action: CounterAction): number {
  switch (action.type) {
    case 'increment': return state + action.amount;
    case 'decrement': return state - action.amount;
    case 'reset':     return 0;
  }
}

type 屬性使用字面值型別,使得 switch 分支在編譯期就能檢查是否漏掉了任何可能的 action。


總結

字面值型別是 TypeScript 中一個 簡潔卻威力十足 的工具。它讓我們能夠:

  • 把「允許的具體值」寫在型別層面,減少執行期錯誤。
  • 搭配聯合型別、as const、函式重載等特性,打造 自說明、可維護 的 API。
  • 在 UI、伺服器回傳、設定檔、狀態管理等各種實務情境中,提供 強型別的安全網。

對於從 JavaScript 轉向 TypeScript 的開發者,建議從 字面值型別 開始練習,逐步擴展到更高階的型別技巧(如映射型別、條件型別)。只要養成在公共介面使用字面值型別的習慣,就能在開發過程中即時捕捉錯誤,提升程式碼品質與團隊協作效率。祝你在 TypeScript 的旅程中,寫出更安全、更可讀的程式碼!