本文 AI 產出,尚未審核

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


簡介

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

對於初學者而言,字面值型別看起來像是小小的語法糖,但在實務開發中,它常與 聯合型別(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 constLogLevel 自動變成 '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 的旅程中,寫出更安全、更可讀的程式碼!