TypeScript 模版字面量型別 (Template Literal Types)

如果你熟悉 JavaScript 的 Template Literals(使用反引號 `\來組合字串),那麼這個 TypeScript 功能你會覺得非常親切。

簡單來說,它允許我們使用「字串插值」的方式來產生新的型別

基本語法

語法跟 JavaScript 一模一樣,只是用在 type 定義中:

type World = 'World';

// 組合出 "Hello World"
type Greeting = `Hello ${World}`;

這看起來很普通,但當它遇到「聯合型別 (Union Types)」時,魔術就發生了。

強大的「組合」能力

當你在模版中放入一個聯合型別時,TypeScript 會自動幫你產生所有可能的排列組合(笛卡兒積)。

type Color = 'red' | 'blue';
type Size = 'S' | 'M' | 'L';

// 自動產生 2 x 3 = 6 種組合
type ProductSKU = `${Color}-${Size}`;
// 結果: "red-S" | "red-M" | "red-L" | "blue-S" | "blue-M" | "blue-L"

這在定義 CSS 類別名稱、API 路徑等場景超級實用。

內建字串操作工具

TypeScript 還提供了幾個內建工具來處理字串的大小寫轉換:

  • Uppercase<S>: 全部轉大寫
  • Lowercase<S>: 全部轉小寫
  • Capitalize<S>: 首字大寫
  • Uncapitalize<S>: 首字小寫

實戰:自動產生事件處理器名稱

假設我們有幾個事件名稱 'click' | 'change',我們想要自動產生對應的 onClickonChange 型別。

type EventName = 'click' | 'change' | 'focus';

// 1. 先把首字變大寫:Click, Change, Focus
// 2. 前面加上 on
type HandlerName = `on${Capitalize<EventName>}`;
// 結果:"onClick" | "onChange" | "onFocus"

進階:搭配 infer 做字串解析

我們可以利用模版字面量來「拆解」字串型別,這有點像是針對型別的 Regular Expression。

範例:拆解 CSS 單位

假設我們想限制輸入值必須是 數字 + px 的格式,並且想把數字提取出來。

// 定義一個特定的字串格式:前面是一堆東西,後面接個 "px"
type PixelString = `${string}px`;

let width: PixelString = '100px'; // OK
// let height: PixelString = "100%"; // 錯誤,沒有 px 結尾

// 提取前面的數字部分
type GetValue<T> = T extends `${infer N}px` ? N : never;

type Val = GetValue<'500px'>; // "500"

範例:提取 API 路由參數

這是一個非常高階的應用,我們可以遞迴地解析字串,抓出裡面所有以 : 開頭的參數。

// 遞迴解析路徑
type ExtractParams<Path> = Path extends `${infer Start}:${infer Param}/${infer Rest}`
  ? Param | ExtractParams<Rest> // 如果中間有參數 (:id/...)
  : Path extends `${infer Start}:${infer Param}` // 如果結尾是參數 (:id)
    ? Param
    : never;

type MyPath = '/users/:userId/posts/:postId';

type Params = ExtractParams<MyPath>;
// 結果:"userId" | "postId"

總結

模版字面量型別讓 TypeScript 的型別系統具備了處理字串的能力。

  1. 基本組合:用 ${} 來串接字串型別。
  2. 自動展開:遇到 Union 會自動產生所有排列組合。
  3. 字串工具:善用 Capitalize 等工具來轉換格式。
  4. 模式比對:搭配 infer 可以解析並提取字串結構(如 API 路由解析)。

這讓 TypeScript 不僅能檢查資料「是不是字串」,還能檢查「字串的內容格式對不對」。