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' |
最佳實踐:
- 把字面值型別放在最外層:讓 API 使用者一眼就能看到允許的值。
- 結合
as const:對於常量資料,讓 TypeScript 自動推導字面值型別,減少冗長。 - 使用
type而非interface:字面值型別本質上是 type alias,使用type更直觀。 - 在函式簽名中明確列出:尤其是事件處理、狀態切換等函式,避免使用寬泛的
string。 - 搭配 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 的旅程中,寫出更安全、更可讀的程式碼!