本文 AI 產出,尚未審核

TypeScript 課程 – 變數與常數宣告(Variables)

主題:常數字面值型別(Literal Inference)


簡介

在 JavaScript 中,我們習慣直接寫 const foo = 1let bar = "hello",而不會去思考 這個值的型別 會是什麼。
進入 TypeScript 後,編譯器會自動推斷變數的型別,這個過程稱為 型別推斷(type inference)。其中最常見、也是最有威力的推斷之一,就是 常數字面值型別(Literal Inference)。

透過字面值型別,我們可以讓變數的型別更精確,進而得到更嚴謹的型別檢查、編譯期錯誤提示,甚至在 IDE 裡獲得更好的自動完成與重構支援。對於寫 可維護、可預測 的程式碼而言,掌握字面值型別是每位 TypeScript 開發者的必備功力。


核心概念

1. 什麼是「字面值型別」?

字面值型別是指 直接由程式碼裡的字面值(literal)所推斷出的具體值型別
例如:

  • 1 → 型別為 1(而非一般的 number
  • "yes" → 型別為 "yes"(而非 string
  • true → 型別為 true(而非 boolean

當我們把這樣的字面值賦值給 const 時,TypeScript 會把變數的型別鎖定為那個具體值;若使用 let,則會退化為寬鬆的基礎型別(numberstringboolean)。

重點const 與字面值結合時,會得到 字面值型別let 則不會。

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

好處 說明
更精確的型別檢查 只能接受特定的值,避免不小心傳入錯誤的字串或數字。
提升 IDE 體驗 自動完成只會顯示合法的選項,減少手打錯誤。
支援 discriminated unions(辨別聯合型別) switchif 判斷時,編譯器能自動窄化型別。
可作為常數枚舉 取代傳統的 enum,讓程式碼更輕量且可 tree‑shake。

3. 基本範例

下面示範 constlet 在字面值推斷上的差異:

// 1️⃣ const 會得到字面值型別
const STATUS_OK = 200;          // 型別是 200(而不是 number)
const MODE = "production";      // 型別是 "production"
const IS_ENABLED = true;        // 型別是 true

// 2️⃣ let 會退化為寬鬆型別
let count = 10;                 // 型別是 number
let env = "development";       // 型別是 string
let flag = false;              // 型別是 boolean

小技巧:如果你想要保留字面值型別,但又需要稍後修改值,可以使用 as const(稍後說明)。

4. as const – 把任何表達式變成字面值型別

有時候我們會在物件或陣列上一次性建立多個常數,這時候直接使用 const 仍會得到寬鬆的型別。as const 讓 TypeScript 把整個結構「凍結」成字面值型別。

// 沒有 as const → 物件屬性是寬鬆型別
const config1 = {
  mode: "development",
  debug: false,
};
 // config1 的型別是 { mode: string; debug: boolean; }

// 使用 as const → 每個屬性都變成字面值型別
const config2 = {
  mode: "production",
  debug: false,
} as const;
// config2 的型別是 { readonly mode: "production"; readonly debug: false; }

注意as const 會把所有屬性標記為 readonly,因此無法再被重新賦值,這正符合「常數」的語意。

5. 字面值型別在聯合型別(Union Types)中的威力

當我們用字面值型別組成聯合型別時,編譯器能夠在條件分支裡自動窄化(type narrowing),讓程式碼更安全。

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

function move(dir: Direction) {
  if (dir === "up") {
    // dir 已被窄化為字面值 "up"
    console.log("向上移動");
  } else if (dir === "down") {
    console.log("向下移動");
  } else {
    // 這裡的 dir 仍然是 "left" | "right"
    console.log(`向 ${dir} 移動`);
  }
}

// 正確使用
move("left");

// 錯誤示範:編譯期會報錯
// move("forward"); // Error: Argument of type '"forward"' is not assignable to parameter of type 'Direction'.

6. 進階範例:使用字面值型別建構「類型安全」的 API

假設我們要寫一個簡易的 HTTP 客戶端,想要限制 method 只能是 GETPOSTPUTDELETE

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

interface RequestOptions {
  url: string;
  method: HttpMethod;
  body?: any;
}

/**
 * 送出 HTTP 請求
 * @param opts 請求設定
 */
function request(opts: RequestOptions) {
  // 這裡的 method 已經是字面值型別,IDE 會只顯示四個合法選項
  console.log(`發送 ${opts.method} 請求至 ${opts.url}`);
}

// 正確使用
request({ url: "/api/users", method: "GET" });

// 錯誤示範 – 編譯期直接捕捉
// request({ url: "/api/users", method: "PATCH" }); // Error

7. 讓數字字面值成為「枚舉」的另一種寫法

在純 TypeScript 中,我們可以用 as const 搭配 typeof 產生類似 enum 的型別集合,且不會產生額外的 JS 代碼。

const STATUS = {
  OK: 200,
  NOT_FOUND: 404,
  INTERNAL_ERROR: 500,
} as const;

// 取出所有可能的值型別
type StatusCode = typeof STATUS[keyof typeof STATUS];

// 使用
function handleResponse(code: StatusCode) {
  if (code === STATUS.OK) {
    console.log("成功");
  } else {
    console.log(`錯誤代碼:${code}`);
  }
}

// 正確
handleResponse(STATUS.NOT_FOUND);

// 錯誤 – 編譯期會報錯
// handleResponse(403); // Error

常見陷阱與最佳實踐

陷阱 說明 解決方式
let 當作 const 使用 let 會退化為寬鬆型別,失去字面值的精確度。 只在不可變的情況下使用 const,或在需要保留字面值時加入 as const
忘記 as const 造成型別過寬 物件或陣列在沒有 as const 時,屬性會變成一般型別。 在宣告常量物件/陣列時,直接寫 as const,或使用 readonly 手動標註。
字面值型別與 enum 混用 enum 會產生額外的 JavaScript 代碼,與字面值型別的 tree‑shake 目標相衝突。 若不需要運行時的映射,盡量使用字面值型別 + as const 替代 enum
過度使用字面值型別導致可讀性下降 把所有字串都寫成字面值聯合型別,會讓型別定義變得冗長。 把相關字面值抽離成獨立的 type,並在註解說明其語意。
在函式參數上使用 as const 失效 as const 只能在表達式層級使用,不能直接寫在參數型別上。 在呼叫端使用 as const,或在函式內部使用泛型 T extends readonly [...] 進行推斷。

最佳實踐小結

  1. 盡量使用 const + 字面值:讓變數自動得到最精確的型別。
  2. 需要凍結物件/陣列時加 as const:保持所有屬性為字面值且只讀。
  3. 把字面值型別抽離成獨立 type:提升可讀性與重用性。
  4. 配合 uniondiscriminated union 使用:讓條件分支自動窄化,減少手寫型別守衛。
  5. 避免不必要的 enum:若僅在編譯期使用,字面值型別更輕量且支援 tree‑shake。

實際應用場景

場景 為何適合使用字面值型別
API 請求方法限制 只允許 `"GET"
狀態碼常數 as const 建立 STATUS 物件,讓函式參數只能接受合法的狀態碼。
UI 主題切換 `type Theme = "light"
表單驗證規則 `type Rule = "required"
Redux/NgRx Action Types 使用字面值字串作為 action type,讓 reducer 的 switch case 自動得到完整型別檢查。
多語系鍵值 as const 定義 TRANSLATIONS 物件,保證取值時不會寫錯鍵名。

總結

  • 字面值型別是 TypeScript 型別推斷中最精確、最具威力的特性之一。
  • 只要搭配 constas const,就能讓變數、物件、陣列「凍結」成具體的值型別,從而得到更嚴謹的編譯期檢查。
  • 聯合型別、辨別聯合(discriminated unions)API 常數 等情境下,字面值型別不僅提升程式安全性,也改善 IDE 的自動完成與重構體驗。
  • 使用時要留意 let 的退化行為、as const 的只讀限制,以及適度抽離型別以維持可讀性。

掌握了字面值型別,你的 TypeScript 程式碼將更安全可維護,同時也能更好地發揮靜態型別的優勢。祝你在 TypeScript 的旅程中寫出更乾淨、錯誤更少的程式!