TypeScript 字面量型別 (Literal Types)

除了 stringnumber 這種寬泛的型別,TypeScript 還允許你把「特定的一個值」當成型別。

這聽起來很廢:「如果變數只能存一個值,那它跟常數有什麼兩樣?」 沒錯!但當它搭配 聯合型別 (Union Types) 一起使用時,就會變得超級強大。

單一字面量型別

這種宣告其實意義不大,因為它就被鎖死了。

let x: 'hello' = 'hello';
// x = "world"; // 錯誤!x 只能是 "hello"

聯合字面量型別 (實用!)

這才是它真正的用法:用來限制一組特定的合法值。這基本上就是現代版的 Enum。

// 變數只能是這三個字串之一
type Alignment = 'left' | 'center' | 'right';

function setAlign(align: Alignment) {
  // ...
}

setAlign('left'); // OK
setAlign('center'); // OK
// setAlign("top");    // 錯誤!"top" 不是合法的 Alignment

這比 Enum 好的地方在於:

  1. 所見即所得:值就是字串,debug 時清楚明瞭。
  2. 零負擔:編譯後完全消失,沒有額外的 JavaScript 程式碼。

數字與布林字面量

除了字串,數字和布林值也可以這樣玩。

type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type HttpSuccess = 200 | 201;

function rollDice(): Dice {
  // return 7; // 錯誤!
  return 1;
}

const vs let 的型別推論

這是一個容易踩雷的地方。

當你用 varlet 宣告變數時,TS 會把它推論為寬泛的型別 (string, number)。 但如果你用 const,TS 會把它推論為字面量型別

// let: 值可能會變,所以型別是 string
let s = 'Hello';

// const: 值永遠不會變,所以型別是 "Hello" (字面量)
const c = 'Hello';

物件屬性的陷阱

const req = { url: 'https://example.com', method: 'GET' };

// 這裡 req.method 被推論為 string (因為物件屬性是可以被修改的)
handleRequest(req.url, req.method);

function handleRequest(url: string, method: 'GET' | 'POST') {
  // ...
}

上面程式碼會報錯,因為 req.methodstring,可以被改成隨意字串,不符合 "GET" | "POST" 的限制。

解法:as const

加上 as const,告訴 TS 說「這個物件的所有屬性都是唯讀的字面量」。

const req = { url: 'https://example.com', method: 'GET' } as const;

// 現在 req.method 的型別就是 "GET" 了
handleRequest(req.url, req.method); // OK