TypeScript 型別體操進階:從入門到精通

前端工程

為什麼 2026 年必須掌握進階 TypeScript

2026 年,TypeScript 已經是前端和 Node.js 專案的標配。但大多數開發者只停留在「給變數加型別」的階段,真正的型別程式設計能力——泛型約束、條件型別、infer 推導、映射型別——才是區分初級和進階 TypeScript 開發者的分水嶺。掌握型別體操,你可以在編譯期捕獲更多錯誤,減少執行時 Bug,同時讓程式碼的自文件化能力大幅提升。

型別體操能解決什麼問題

  • 編譯期校驗:將執行時錯誤提前到編譯期,如 API 回應欄位缺失、表單校驗規則不一致
  • 程式碼提示增強:精準的智慧提示讓開發效率翻倍,減少查閱文件的頻率
  • 重構安全網:型別系統是重構的最強保障,改一個欄位型別,所有參考處立即報錯
  • 領域建模:用 branded types、discriminated unions 在型別層面表達業務規則
  • 零執行時開銷:所有型別體操在編譯後全部擦除,不影響包體積和效能

從簡單型別到型別程式設計的演進

// 初級:給變數加型別
const username: string = '張三'
const age: number = 25

// 中級:使用泛型和介面
interface ApiResponse<T> {
  code: number
  data: T
  message: string
}

// 進階:型別程式設計 - 在型別層面做計算
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K]
}

type User = {
  name: string
  profile: { avatar: string; bio: string }
}
type FrozenUser = DeepReadonly<User>
// { readonly name: string; readonly profile: { readonly avatar: string; readonly bio: string } }

💡 使用 JSON 轉 TypeScript 工具 快速從 API 回應產生型別定義,然後在此基礎上做進階型別程式設計。


泛型約束與預設值

extends 約束泛型範圍

泛型約束讓你限制型別參數的範圍,避免傳入不合法的型別。

// 約束 T 必須有 id 屬性
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id)
}

interface User { id: number; name: string }
interface Product { id: number; title: string; price: number }

const users: User[] = [{ id: 1, name: '張三' }]
const products: Product[] = [{ id: 1, title: 'TypeScript 書', price: 99 }]

findById(users, 1)      // ✅ User | undefined
findById(products, 1)   // ✅ Product | undefined
findById([1, 2, 3], 1)  // ❌ number 沒有 id 屬性

keyof 約束屬性名

// 約束 K 必須是 T 的屬性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: '張三', age: 25, email: 'zhangsan@test.com' }
getProperty(user, 'name')   // ✅ string
getProperty(user, 'age')    // ✅ number
getProperty(user, 'phone')  // ❌ 'phone' 不是 user 的屬性

泛型預設值

interface PaginatedResponse<T, Meta = { total: number; page: number }> {
  data: T[]
  meta: Meta
}

type UserList = PaginatedResponse<User>
// meta 預設為 { total: number; page: number }

type CustomMeta = { total: number; page: number; hasNext: boolean }
type UserListCustom = PaginatedResponse<User, CustomMeta>
// meta 使用自訂型別

多泛型約束組合

// T 必須同時滿足兩個約束
type RequireBoth<T extends A & B, A, B> = T

interface HasId { id: number }
interface HasName { name: string }

function mergeEntities<T extends HasId & HasName>(a: T, b: T): T[] {
  return [a, b]
}

mergeEntities({ id: 1, name: '張三' }, { id: 2, name: '李四' }) // ✅
mergeEntities({ id: 1 }, { id: 2 }) // ❌ 缺少 name

條件型別:extends ? :

基本語法

條件型別就像型別層面的三元運算式:T extends U ? X : Y

type IsString<T> = T extends string ? 'yes' : 'no'

type A = IsString<string>   // 'yes'
type B = IsString<number>   // 'no'
type C = IsString<'hello'>  // 'yes'(字面量型別也匹配 string)

分散式條件型別

當 T 是聯合型別時,條件型別會自動分發:

type ToArray<T> = T extends unknown ? T[] : never

type Result = ToArray<string | number>
// 分發過程:ToArray<string> | ToArray<number>
// 結果:string[] | number[]

// 注意:這不是 (string | number)[]

阻止分發

[T] 包裹來阻止分發行為:

type ToArrayNoDistribute<T> = [T] extends [unknown] ? T[] : never

type Result2 = ToArrayNoDistribute<string | number>
// 結果:(string | number)[],不會分發

實戰:型別過濾

// 從聯合型別中過濾掉某些型別
type Exclude<T, U> = T extends U ? never : T
type Extract<T, U> = T extends U ? T : never

type AllTypes = string | number | boolean | null | undefined

type NonNull = Exclude<AllTypes, null | undefined>
// string | number | boolean

type OnlyString = Extract<AllTypes, string>
// string

// 過濾物件型別中的某些屬性
type PickByValue<T, ValueType> = {
  [K in keyof T as T[K] extends ValueType ? K : never]: T[K]
}

interface FormState {
  username: string
  age: number
  email: string
  isActive: boolean
}

type StringFields = PickByValue<FormState, string>
// { username: string; email: string }

infer 關鍵字深度解析

infer 基礎:在條件型別中提取型別

infer 只能在 extends 條件型別中使用,用於宣告一個待推斷的型別變數。

// 提取函式回傳值型別
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function getUser() {
  return { name: '張三', age: 25 }
}

type UserType = ReturnType<typeof getUser>
// { name: string; age: number }

// 提取函式參數型別
type Parameters<T> = T extends (...args: infer P) => any ? P : never

function greet(name: string, age: number): string {
  return `Hello, ${name}, age ${age}`
}

type GreetParams = Parameters<typeof greet>
// [string, number]

提取 Promise 內部型別

type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T

type P1 = Awaited<Promise<string>>          // string
type P2 = Awaited<Promise<Promise<number>>> // number(遞迴解包)
type P3 = Awaited<string>                   // string(非 Promise 直接回傳)

提取陣列元素型別

type ElementOf<T> = T extends (infer E)[] ? E : never

type Items = ElementOf<string[]>       // string
type Nums = ElementOf<number[]>        // number
type Mixed = ElementOf<(string | number)[]> // string | number

// 提取元組第一個元素
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never

type First = Head<[string, number, boolean]> // string

// 提取元組最後一個元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never

type Tail = Last<[string, number, boolean]> // boolean

多 infer 協同推斷

// 提取類別建構函式的實例型別
type InstanceOf<T> = T extends new (...args: any[]) => infer I ? I : never

class UserService {
  constructor(private id: number) {}
  getUser() { return { id: this.id } }
}

type ServiceInstance = InstanceOf<typeof UserService>
// UserService

// 同時提取請求和回應型別
type ApiTypes<T> = T extends (req: infer Req) => Promise<infer Res>
  ? { request: Req; response: Res }
  : never

async function updateUser(req: { id: number; name: string }): Promise<{ success: boolean }> {
  return { success: true }
}

type UpdateUserTypes = ApiTypes<typeof updateUser>
// { request: { id: number; name: string }; response: { success: boolean } }

映射型別:從零實作內建工具型別

Record 實作

// 內建 Record<K, V> 的實作原理
type MyRecord<K extends keyof any, V> = {
  [P in K]: V
}

type UserRoles = MyRecord<'admin' | 'editor' | 'viewer', boolean>
// { admin: boolean; editor: boolean; viewer: boolean }

// 實戰:建立列舉映射
type StatusMap = MyRecord<'pending' | 'active' | 'closed', { label: string; color: string }>

const statusConfig: StatusMap = {
  pending: { label: '待處理', color: '#f59e0b' },
  active: { label: '進行中', color: '#10b981' },
  closed: { label: '已關閉', color: '#6b7280' },
}

Partial 和 Required 實作

// Partial:所有屬性變為可選
type MyPartial<T> = {
  [K in keyof T]?: T[K]
}

// Required:所有屬性變為必選
type MyRequired<T> = {
  [K in keyof T]-?: T[K]
}

// 深層 Partial
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepPartial<T[K]>
    : T[K]
}

interface Config {
  db: { host: string; port: number }
  cache: { ttl: number; max: number }
}

type PartialConfig = DeepPartial<Config>
// { db?: { host?: string; port?: number }; cache?: { ttl?: number; max?: number } }

Pick 和 Omit 實作

// Pick:選取部分屬性
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

// Omit:排除部分屬性
type MyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
}

interface FullUser {
  id: number
  name: string
  email: string
  password: string
  createdAt: string
}

// 安全的使用者資訊,排除密碼
type SafeUser = MyOmit<FullUser, 'password'>
// { id: number; name: string; email: string; createdAt: string }

// 只更新部分欄位
type UserUpdate = MyPartial<MyPick<FullUser, 'name' | 'email'>>
// { name?: string; email?: string }

鍵重映射(Key Remapping)

TypeScript 4.1+ 支援用 as 對鍵進行重映射:

// 屬性名加前綴
type AddPrefix<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K]
}

interface ApiData { name: string; age: number }
type PrefixedData = AddPrefix<ApiData, 'user'>
// { userName: string; userAge: number }

// 將所有屬性變為 getter
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

type UserGetters = Getters<{ name: string; age: number }>
// { getName: () => string; getAge: () => number }

模板字面量型別

基礎用法

type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

// CSS 屬性型別
type CSSUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw'
type CSSValue = `${number}${CSSUnit}`

const width: CSSValue = '100px'   // ✅
const height: CSSValue = '50vh'   // ✅
const invalid: CSSValue = 'abc'   // ❌ 不匹配模板

結合映射型別實作型別安全的事件系統

type Events = {
  click: { x: number; y: number }
  keydown: { key: string; code: string }
  resize: { width: number; height: number }
}

type EventHandlerMap = {
  [K in keyof Events as `on${Capitalize<string & K>}`]: (payload: Events[K]) => void
}

type Listeners = EventHandlerMap
// {
//   onClick: (payload: { x: number; y: number }) => void
//   onKeyDown: (payload: { key: string; code: string }) => void
//   onResize: (payload: { width: number; height: number }) => void
// }

const listeners: Listeners = {
  onClick: (e) => console.log(e.x, e.y),
  onKeyDown: (e) => console.log(e.key),
  onResize: (e) => console.log(e.width),
}

字串解析型別

// 解析路由參數
type ParseRouteParams<S extends string> =
  S extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ParseRouteParams<Rest>
    : S extends `${string}:${infer Param}`
      ? Param
      : never

type Params = ParseRouteParams<'/user/:id/post/:postId'>
// 'id' | 'postId'

// 解析版本號
type ParseVersion<V extends string> =
  V extends `${infer Major}.${infer Minor}.${infer Patch}`
    ? { major: Major; minor: Minor; patch: Patch }
    : never

type V = ParseVersion<'1.2.3'>
// { major: '1'; minor: '2'; patch: '3' }

遞迴型別

深層 Readonly

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K]
}

const config: DeepReadonly<{
  db: { host: string; port: number }
  cache: { ttl: number }
}> = {
  db: { host: 'localhost', port: 3306 },
  cache: { ttl: 3600 },
}

// config.db.host = '127.0.0.1'  // ❌ 唯讀

深層 Partial 與合併

type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T

type DeepMerge<A, B> = {
  [K in keyof A | keyof B]: K extends keyof B
    ? B[K] extends object
      ? K extends keyof A
        ? A[K] extends object
          ? DeepMerge<A[K], B[K]>
          : B[K]
        : B[K]
      : B[K]
    : K extends keyof A
      ? A[K]
      : never
}

遞迴元組操作

// 元組反轉
type Reverse<T extends any[]> = T extends [infer H, ...infer R]
  ? [...Reverse<R>, H]
  : []

type Reversed = Reverse<[1, 2, 3, 4]> // [4, 3, 2, 1]

// 元組扁平化
type Flatten<T extends any[]> = T extends [infer H, ...infer R]
  ? H extends any[]
    ? [...Flatten<H>, ...Flatten<R>]
    : [H, ...Flatten<R>]
  : []

type Flat = Flatten<[1, [2, 3], [4, [5, 6]]]> // [1, 2, 3, 4, 5, 6]

// 字串長度
type StrLen<S extends string, Acc extends any[] = []> =
  S extends `${string}${infer Rest}`
    ? StrLen<Rest, [...Acc, 0]>
    : Acc['length']

type Len = StrLen<'hello'> // 5

型別層級程式設計:算術與字串操作

型別層級加法

// 利用元組長度實作加法
type BuildTuple<N extends number, T extends any[] = []> =
  T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>

type Add<A extends number, B extends number> =
  [...BuildTuple<A>, ...BuildTuple<B>]['length']

type Sum = Add<3, 5> // 8

// 型別層級減法
type Subtract<A extends number, B extends number> =
  BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
    ? Rest['length']
    : never

type Diff = Subtract<10, 4> // 6

型別層級字串操作

// 字串替換
type Replace<S extends string, From extends string, To extends string> =
  From extends ''
    ? S
    : S extends `${infer Before}${From}${infer After}`
      ? `${Before}${To}${After}`
      : S

type Replaced = Replace<'hello world', 'world', 'TypeScript'>
// 'hello TypeScript'

// 駝峰轉底線
type CamelToSnake<S extends string> =
  S extends `${infer H}${infer Rest}`
    ? H extends Uppercase<H>
      ? `_${Lowercase<H>}${CamelToSnake<Rest>}`
      : `${H}${CamelToSnake<Rest>}`
    : S

type Snake = CamelToSnake<'userName'> // 'user_name'

可辨識聯合型別

基礎模式

// 用 kind 欄位做辨識
interface Circle { kind: 'circle'; radius: number }
interface Rectangle { kind: 'rectangle'; width: number; height: number }
interface Triangle { kind: 'triangle'; base: number; height: number }

type Shape = Circle | Rectangle | Triangle

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2
    case 'rectangle': return shape.width * shape.height
    case 'triangle': return 0.5 * shape.base * shape.height
  }
}

狀態機模式

type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

function handleState<T>(state: RequestState<T>) {
  switch (state.status) {
    case 'idle': return '等待請求'
    case 'loading': return '載入中...'
    case 'success': return `資料: ${JSON.stringify(state.data)}`
    case 'error': return `錯誤: ${state.error.message}`
  }
}

// 狀態轉換是型別安全的
const idleState: RequestState<string> = { status: 'idle' }
// idleState.data // ❌ idle 狀態沒有 data
const successState: RequestState<string> = { status: 'success', data: 'hello' }
// successState.data // ✅ string

窮盡檢查

// 利用 never 型別確保所有分支都被處理
function assertNever(x: never): never {
  throw new Error(`未處理的型別: ${JSON.stringify(x)}`)
}

function processShape(shape: Shape) {
  switch (shape.kind) {
    case 'circle': return /* ... */ ''
    case 'rectangle': return /* ... */ ''
    case 'triangle': return /* ... */ ''
    default: return assertNever(shape) // 如果漏掉某個 case,這裡會報錯
  }
}

Branded Types:型別安全的利器

基礎 Branded Type

// 用品牌型別區分相同結構但不同意義的型別
type Brand<T, B extends string> = T & { __brand: B }

type UserId = Brand<number, 'UserId'>
type OrderId = Brand<number, 'OrderId'>

function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }

const uid = 1 as UserId
const oid = 2 as OrderId

getUser(uid) // ✅
getUser(oid) // ❌ OrderId 不能賦給 UserId
getOrder(1)  // ❌ number 不能賦給 UserId

型別安全的 ID 系統

function createId<T extends string>(brand: T) {
  return (id: number) => id as Brand<number, T>
}

const userId = createId('UserId')
const orderId = createId('OrderId')

function deleteUser(id: UserId) { /* ... */ }

deleteUser(userId(1))  // ✅
deleteUser(orderId(1)) // ❌ 型別安全阻止了錯誤

型別安全的貨幣系統

type USD = Brand<number, 'USD'>
type CNY = Brand<number, 'CNY'>

function addUSD(a: USD, b: USD): USD {
  return (a + b) as USD
}

function addCNY(a: CNY, b: CNY): CNY {
  return (a + b) as CNY
}

const price1 = 100 as USD
const price2 = 50 as USD
const cnyPrice = 200 as CNY

addUSD(price1, price2)    // ✅
addUSD(price1, cnyPrice)  // ❌ 不能把 CNY 和 USD 混加

satisfies 運算子

基礎用法

satisfies 讓你在不拓寬型別的情況下驗證型別相容性:

// ❌ 用型別註解會拓寬型別
const colors: Record<string, string | string[]> = {
  primary: '#3b82f6',
  secondary: '#6b7280',
  gradients: ['#3b82f6', '#6b7280'],
}
// colors.primary.toUpperCase() // ❌ 型別是 string | string[],不能直接 toUpperCase

// ✅ 用 satisfies 保持字面量型別
const themeColors = {
  primary: '#3b82f6',
  secondary: '#6b7280',
  gradients: ['#3b82f6', '#6b7280'],
} satisfies Record<string, string | string[]>

themeColors.primary.toUpperCase()  // ✅ 型別是 '#3b82f6'
themeColors.gradients[0].toUpperCase() // ✅ 型別是 string

配置物件校驗

interface FeatureConfig {
  enabled: boolean
  label: string
}

const features = {
  darkMode: { enabled: true, label: '深色模式' },
  notifications: { enabled: false, label: '通知' },
  analytics: { enabled: true, label: '分析' },
} satisfies Record<string, FeatureConfig>

// features 的型別保留了所有鍵名,不會變成 Record<string, FeatureConfig>
features.darkMode.enabled   // ✅ boolean
features.notifications.label // ✅ string
// features.nonExist.enabled  // ❌ 屬性不存在

const 型別參數

泛型推斷字面量型別

TypeScript 5.0+ 的 const 型別參數讓泛型推斷保留字面量型別:

// ❌ 沒有 const,T 被推斷為 string
function createRoute<T>(path: T) {
  return { path }
}
const route = createRoute('/user/:id')
// route.path 型別是 string,不是 '/user/:id'

// ✅ 使用 const 型別參數
function createRouteConst<const T>(path: T) {
  return { path }
}
const routeConst = createRouteConst('/user/:id')
// routeConst.path 型別是 '/user/:id'

結合模板字面量型別

function defineEndpoints<const T extends Record<string, string>>(routes: T) {
  type RouteKeys = keyof T
  return routes as T & { _routeKeys: RouteKeys }
}

const api = defineEndpoints({
  getUsers: '/api/users',
  getUser: '/api/users/:id',
  createUser: '/api/users',
})

// api 的鍵名被保留為字面量型別 'getUsers' | 'getUser' | 'createUser'

實戰模式

API 回應型別安全

interface ApiRoutes {
  '/api/users': {
    response: { users: { id: number; name: string }[] }
  }
  '/api/users/:id': {
    params: { id: number }
    response: { id: number; name: string; email: string }
  }
  '/api/orders': {
    query: { status?: string; page?: number }
    response: { orders: { id: number; total: number }[]; total: number }
  }
}

type ApiRoute = keyof ApiRoutes

async function apiCall<
  Route extends ApiRoute,
  Config extends ApiRoutes[Route]
>(
  route: Route,
  options: Omit<Config, 'response'> extends object ? Config : {}
): Promise<Config extends { response: infer R } ? R : never> {
  const res = await fetch(route as string)
  return res.json()
}

// 型別安全的 API 呼叫
const users = await apiCall('/api/users', {})
// users 型別: { users: { id: number; name: string }[] }

表單校驗型別

type Validator<T> = (value: T) => string | null

type FormValidators<T> = {
  [K in keyof T]?: Validator<T[K]> | Validator<T[K]>[]
}

interface LoginForm {
  username: string
  password: string
  remember: boolean
}

const loginValidators: FormValidators<LoginForm> = {
  username: (v) => v.length < 3 ? '使用者名稱至少3個字元' : null,
  password: (v) => v.length < 6 ? '密碼至少6個字元' : null,
}

type ValidationError<T> = {
  [K in keyof T]?: string[]
}

function validateForm<T>(
  data: T,
  validators: FormValidators<T>
): ValidationError<T> {
  const errors: ValidationError<T> = {}
  for (const key in validators) {
    const validator = validators[key]
    const value = data[key]
    if (validator && value !== undefined) {
      const result = Array.isArray(validator)
        ? validator.map(v => v(value)).filter(Boolean)
        : validator(value)
      if (result) errors[key] = Array.isArray(result) ? result : [result]
    }
  }
  return errors
}

事件系統型別

interface EventMap {
  'user:login': { userId: number; timestamp: number }
  'user:logout': { userId: number }
  'cart:add': { productId: number; quantity: number }
  'cart:remove': { productId: number }
  'order:create': { orderId: number; total: number }
}

type EventHandler<T extends keyof EventMap> = (payload: EventMap[T]) => void

class TypeSafeEventEmitter {
  private handlers = new Map<string, Set<EventHandler<any>>>()

  on<K extends keyof EventMap>(event: K, handler: EventHandler<K>) {
    if (!this.handlers.has(event as string)) {
      this.handlers.set(event as string, new Set())
    }
    this.handlers.get(event as string)!.add(handler)
    return () => this.off(event, handler)
  }

  off<K extends keyof EventMap>(event: K, handler: EventHandler<K>) {
    this.handlers.get(event as string)?.delete(handler)
  }

  emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
    this.handlers.get(event as string)?.forEach(h => h(payload))
  }
}

const emitter = new TypeSafeEventEmitter()
emitter.on('user:login', (e) => console.log(e.userId))  // ✅
emitter.emit('cart:add', { productId: 1, quantity: 2 })  // ✅
// emitter.emit('cart:add', { wrong: true })  // ❌ 型別錯誤

常見型別錯誤及修復

錯誤 1:型別被意外拓寬

// ❌ 物件字面量型別被拓寬
const config = { port: 3000, host: 'localhost' }
// config.port 型別是 number,不是 3000

// ✅ 使用 as const
const configConst = { port: 3000, host: 'localhost' } as const
// configConst.port 型別是 3000

錯誤 2:泛型推斷為聯合型別

// ❌ T 被推斷為 string | number,不是聯合分發
function wrapInArray<T>(value: T): T[] {
  return [value]
}
const result = wrapInArray(Math.random() > 0.5 ? 'hello' : 42)
// result 型別是 (string | number)[],不是 string[] | number[]

// ✅ 使用函式多載或條件型別
function wrapInArrayFixed<T>(value: T): [T] {
  return [value]
}

錯誤 3:循環參考

// ❌ 型別循環參考
interface TreeNode {
  value: string
  children: TreeNode[]  // ✅ 介面支援循環參考
}

// ❌ type 別名不支援循環參考
// type Node = { value: string; children: Node[] } // 可能報錯

// ✅ 使用介面或加入泛型間接參考
type Node<T = Node> = { value: string; children: T[] }

錯誤 4:any 污染

// ❌ any 會污染所有接觸它的型別
function parseJSON(str: string): any {
  return JSON.parse(str)
}
const data = parseJSON('{"name":"張三"}')
data.nonExist.method() // 不報錯,但執行時當掉

// ✅ 使用 unknown
function parseJSONSafe(str: string): unknown {
  return JSON.parse(str)
}
const safeData = parseJSONSafe('{"name":"張三"}')
// safeData.name // ❌ unknown 上不存在 name 屬性
if (typeof safeData === 'object' && safeData !== null && 'name' in safeData) {
  console.log((safeData as { name: string }).name) // ✅
}

型別除錯技巧

檢視中間型別

// 利用型別錯誤檢視型別
type Debug<T> = { [K in keyof T]: T[K] }

type Result = Debug<SomeComplexType>
// 滑鼠懸停 Result 即可看到展開後的型別

// 使用工具型別輔助除錯
type Prettify<T> = { [K in keyof T]: T[K] } & {}

type Nested = { a: { b: { c: string } }; d: number }
type PrettyNested = Prettify<Nested>
// 展開為 { a: { b: { c: string } }; d: number }

條件型別逐步除錯

// 逐步拆解複雜條件型別
type Step1<T> = T extends string ? true : false
type Step2<T> = T extends `${infer H}${infer R}` ? { head: H; rest: R } : never

// 逐層驗證
type S1 = Step1<'hello'>   // true
type S2 = Step2<'hello'>   // { head: 'h'; rest: 'ello' }

常見問題 FAQ

Q1: 型別體操會影響執行時效能嗎?

不會。TypeScript 的所有型別資訊在編譯後都會被完全擦除,型別體操只存在於編譯期,對執行時效能零影響。

Q2: 什麼時候該用型別體操,什麼時候該用執行時校驗?

兩者搭配使用:型別體操負責編譯期校驗(開發時捕獲錯誤),執行時校驗(如 Zod、Joi)負責外部資料(API 回應、使用者輸入)的校驗。型別體操不能替代執行時校驗。

Q3: 遞迴型別深度有限制嗎?

。TypeScript 對遞迴型別有深度限制(約 1000 層),超出會報「Type instantiation is excessively deep」錯誤。實際使用中,10 層以內的遞迴通常足夠。

Q4: 如何在團隊中推廣型別體操?

  1. 從簡單的工具型別開始(Partial、Pick、Omit)
  2. 建立團隊的型別工具庫(types/utils.ts
  3. 在 Code Review 中要求關鍵函式有完整型別
  4. 使用 JSON 格式化工具 驗證 JSON 資料結構,搭配型別定義確保一致性

Q5: satisfies 和型別註解有什麼區別?

型別註解(:)會拓寬型別為註解型別,satisfies 只做校驗不改變推斷型別。需要保持字面量型別精度時用 satisfies,需要明確約束型別時用型別註解。

💡 使用 Base64 編碼工具 處理型別定義中的內嵌資料,減少外部依賴。

本站提供瀏覽器本地工具,免註冊即可試用 →

#TypeScript#类型体操#泛型#条件类型#教程