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 | 61.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}` | 組合字串類型 |
| Uppercase | Uppercase<T> | 轉大寫 |
| Capitalize | Capitalize<T> | 首字母大寫 |
> **應用場景**:
- 狀態枚舉(status、mode)
- 事件名稱
- CSS 值
- API 路由
進階挑戰
- 定義一個
HTTPStatus類型,包含常見狀態碼(200、201、400、404、500) - 使用模板字面量建立 CSS 顏色格式:
rgb(${number}, ${number}, ${number}) - 建立一個類型,將
'user-profile-page'轉換為駝峰式