跳至主要內容
Skip to content

TypeScript 字面量類型與模板字面量

字面量類型讓你可以指定「具體的值」作為類型。本篇將深入介紹這個強大的特性。


一、 字面量類型基礎

1.1 字串字面量

typescript
// 只能是 'hello' 這個具體值
let greeting: 'hello' = 'hello'

// greeting = 'hi'  // 錯誤!

// 常用於聯合類型
type Direction = 'up' | 'down' | 'left' | 'right'

function move(direction: Direction) {
  console.log(`Moving ${direction}`)
}

move('up') // 
// move('forward')  // 不是有效的方向

1.2 數字字面量

typescript
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6

function roll(): DiceRoll {
  return Math.floor(Math.random() * 6 + 1) as DiceRoll
}

const result = roll() // 1 | 2 | 3 | 4 | 5 | 6

1.3 布林字面量

typescript
type True = true
type False = false

// 通常與泛型配合使用
type IsString<T> = T extends string ? true : false

type A = IsString<'hello'> // true
type B = IsString<123> // false

二、 const 斷言

2.1 let vs const

typescript
let a = 'hello' // 類型:string
const b = 'hello' // 類型:'hello'(字面量類型)

// let 可以重新賦值,所以類型較寬
// const 不能重新賦值,所以使用字面量類型

2.2 as const

typescript
// 物件屬性使用 as const
const config = {
  api: 'https://api.example.com',
  timeout: 5000,
} as const

// 類型:{ readonly api: 'https://api.example.com'; readonly timeout: 5000 }

// config.api = 'other'  // 錯誤!唯讀

// 陣列使用 as const
const colors = ['red', 'green', 'blue'] as const
// 類型:readonly ['red', 'green', 'blue']

// colors.push('yellow')  // 錯誤!

2.3 搭配函式使用

typescript
function defineConfig<T>(config: T): T {
  return config
}

// 使用 as const 保留字面量類型
const config = defineConfig({
  mode: 'production',
  port: 3000,
} as const)

// config.mode 的類型是 'production' 而不是 string

三、 模板字面量類型

3.1 基本語法

typescript
type World = 'world'
type Greeting = `hello ${World}` // 'hello world'

type EmailLocale = `${string}@${string}.${string}`
// 例如:'user@example.com'

3.2 聯合類型展開

typescript
type Size = 'small' | 'medium' | 'large'
type Color = 'red' | 'blue' | 'green'

type SizedColor = `${Size}-${Color}`
// 'small-red' | 'small-blue' | 'small-green' |
// 'medium-red' | 'medium-blue' | 'medium-green' |
// 'large-red' | 'large-blue' | 'large-green'

3.3 事件名稱

typescript
type EventName = 'click' | 'focus' | 'blur'

// 生成處理器名稱
type Handler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

// 生成移除事件名稱
type RemoveEvent = `remove${Capitalize<EventName>}Listener`
// 'removeClickListener' | 'removeFocusListener' | 'removeBlurListener'

四、 內建字串操作類型

4.1 Uppercase / Lowercase

typescript
type Loud = Uppercase<'hello'> // 'HELLO'
type Quiet = Lowercase<'HELLO'> // 'hello'

4.2 Capitalize / Uncapitalize

typescript
type Capitalized = Capitalize<'hello'> // 'Hello'
type Uncapitalized = Uncapitalize<'Hello'> // 'hello'

4.3 組合使用

typescript
type CamelCase<S extends string> = S extends `${infer First}-${infer Rest}`
  ? `${Lowercase<First>}${Capitalize<CamelCase<Rest>>}`
  : Lowercase<S>

type Result = CamelCase<'my-component-name'>
// 'myComponentName'

五、 實用範例

5.1 CSS 屬性

typescript
type CSSUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw'
type CSSValue = `${number}${CSSUnit}`

function setWidth(value: CSSValue) {
  // ...
}

setWidth('100px') // 
setWidth('2rem') // 
// setWidth('100')   // 缺少單位
// setWidth('abc')   // 無效格式

5.2 HTTP 方法與路徑

typescript
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/posts' | '/comments'

type ApiRoute = `${Method} ${Endpoint}`
// 'GET /users' | 'GET /posts' | 'GET /comments' |
// 'POST /users' | 'POST /posts' | 'POST /comments' | ...

function request(route: ApiRoute) {
  const [method, path] = route.split(' ')
  // ...
}

request('GET /users') // 
request('POST /posts') //

5.3 Vue 事件

typescript
// Vue 的 emit 事件命名慣例
type EmitEvent<T extends string> = `update:${T}`

type Events = EmitEvent<'modelValue' | 'visible' | 'selected'>
// 'update:modelValue' | 'update:visible' | 'update:selected'

5.4 物件路徑

typescript
type PathOf<T, K extends keyof T = keyof T> = K extends string
  ? T[K] extends object
    ? K | `${K}.${PathOf<T[K]>}`
    : K
  : never

interface User {
  name: string
  address: {
    city: string
    country: string
  }
}

type UserPaths = PathOf<User>
// 'name' | 'address' | 'address.city' | 'address.country'

六、 進階技巧

6.1 解析模板字面量

typescript
// 使用 infer 解析
type ParseRoute<T extends string> = T extends `${infer Method} ${infer Path}`
  ? { method: Method; path: Path }
  : never

type Route = ParseRoute<'GET /users'>
// { method: 'GET'; path: '/users' }

6.2 動態屬性名

typescript
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Person {
  name: string
  age: number
}

type PersonGetters = Getters<Person>
// { getName: () => string; getAge: () => number }

6.3 路由參數

typescript
type ExtractParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<Rest>
    : T extends `${string}:${infer Param}`
    ? Param
    : never

type Params = ExtractParams<'/users/:id/posts/:postId'>
// 'id' | 'postId'

總結

類型語法用途
字串字面量'value'限定特定字串
數字字面量1 | 2 | 3限定特定數字
as const{ } as const深度字面量 + 唯讀
模板字面量`${A}-${B}`組合字串類型
UppercaseUppercase<T>轉大寫
CapitalizeCapitalize<T>首字母大寫

> **應用場景**:

  • 狀態枚舉(status、mode)
  • 事件名稱
  • CSS 值
  • API 路由

進階挑戰

  1. 定義一個 HTTPStatus 類型,包含常見狀態碼(200、201、400、404、500)
  2. 使用模板字面量建立 CSS 顏色格式:rgb(${number}, ${number}, ${number})
  3. 建立一個類型,將 'user-profile-page' 轉換為駝峰式

延伸閱讀與資源