TypeScript 5.8模式匹配實戰:7個進階型別級程式設計模式從入門到精通

前端工程

型別程式設計的四大痛點

你寫了一個聯合型別string | number | boolean,結果每個分支都要手動處理;條件型別巢狀三層,同事看了直搖頭;ReturnType<typeof fn>推導出來的是any;執行時拿到的資料和型別定義對不上。TypeScript的型別系統明明是圖靈完備的,但你只會用PartialPick。聯合型別太寬泛、條件型別巢狀難讀、型別推導不夠精確、執行時型別檢查缺失——這四個痛點讓大多數開發者的型別程式設計停留在「能編譯就行」的水平。

本文將從核心概念出發,帶你完成條件型別與infer→模板字面量型別→遞迴型別→映射型別→型別級數學→執行時型別安全→型別級狀態機的7個進階模式,從型別推導到執行時校驗,一步不落。


核心概念

概念 說明
條件型別 T extends U ? X : Y,根據型別關係選擇不同型別分支,是型別級程式設計的基礎
分散式條件型別 T為聯合型別時,條件型別自動對每個成員分別求值,實現型別級映射
infer關鍵字 在條件型別中宣告型別變數,從待推斷的型別中提取子型別,如infer R
模板字面量型別 `prefix_${T}`,對字串字面量型別進行拼接和模式匹配
遞迴型別 型別定義中引用自身,實現深度遍歷和複雜型別變換,受遞迴深度限制
映射型別 { [K in keyof T]: ... },基於已有型別生成新型別,是型別變換的核心工具
型別收窄 透過typeofininstanceof等在控制流中縮小型別範圍
satisfies運算子 expr satisfies Type,驗證運算式滿足型別但不拓寬推斷型別,TypeScript 4.9+

型別級程式設計流程

型別推導:
條件型別(extends) → infer提取 → 分散式求值 → 型別收窄

型別變換:
映射型別(keyof) → 模板字面量(拼接/匹配) → 遞迴遍歷(深度變換) → 型別組合

型別安全:
satisfies驗證 → Zod執行時校驗 → 型別級狀態機 → 編譯時+執行時一致性

問題分析:型別級程式設計的5大挑戰

  1. 型別可讀性差:巢狀的條件型別和遞迴型別難以理解,type X = T extends A ? B extends C ? D : E : F三層巢狀讓人崩潰,缺乏型別級註解和中間命名
  2. 編譯速度慢:複雜遞迴型別和大型聯合型別的實例化消耗大量編譯器資源,10層遞迴型別讓tsc慢5倍
  3. 型別與執行時不一致:TypeScript型別在執行時被擦除,編譯時精確的型別定義無法保證執行時資料安全,API回應和型別定義脫節
  4. 遞迴深度限制:TypeScript遞迴型別預設深度限制約1000層,複雜型別變換容易觸發Type instantiation is excessively deep
  5. 除錯困難:型別錯誤訊息冗長難讀,infer推導失敗時沒有中間狀態可查,複雜型別變換的除錯手段幾乎為零

分步實操:7個進階模式

模式1:條件型別與infer模式匹配

type MatchResult<T, Pattern> = T extends Pattern ? { matched: true; value: T } : { matched: false };

type ExtractPromiseValue<T> = T extends Promise<infer V> ? V : never;
type ExtractArrayElement<T> = T extends (infer E)[] ? E : never;
type ExtractFunctionReturn<T> = T extends (...args: any[]) => infer R ? R : never;

type Unwrap<T> = T extends Promise<infer V>
  ? V extends Promise<infer V2>
    ? V2
    : V
  : T;

type DeepUnwrap<T> = T extends Promise<infer V> ? DeepUnwrap<V> : T;

type ParseRoute<S extends string> =
  S extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ParseRoute<Rest>
    : S extends `${string}:${infer Param}`
      ? Param
      : never;

type RouteParams = ParseRoute<'/api/users/:userId/posts/:postId'>;

type ExtractConstructorParams<T> = T extends new (...args: infer P) => any ? P : never;

type EventMap = {
  click: { x: number; y: number };
  focus: { element: string };
  keydown: { key: string; code: string };
};

type ExtractEventData<T extends keyof EventMap> = EventMap[T];

type InferTuple<T extends any[]> = T extends [infer First, ...infer Rest]
  ? [First, ...InferTuple<Rest>]
  : [];

type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T;

模式2:模板字面量型別模式匹配

type TrimLeft<S extends string> = S extends ` ${infer Rest}` ? TrimLeft<Rest> : S;
type TrimRight<S extends string> = S extends `${infer Rest} ` ? TrimRight<Rest> : S;
type Trim<S extends string> = TrimLeft<TrimRight<S>>;

type CapitalizeWords<S extends string> =
  S extends `${infer First} ${infer Rest}`
    ? `${Capitalize<First>} ${CapitalizeWords<Rest>}`
    : Capitalize<S>;

type KebabToCamel<S extends string> =
  S extends `${infer First}-${infer Rest}`
    ? `${First}${Capitalize<KebabToCamel<Rest>>}`
    : S;

type CamelToKebab<S extends string> =
  S extends `${infer First}${infer Rest}`
    ? First extends Uppercase<First>
      ? `${Lowercase<First>}${CamelToKebab<Rest>}`
      : `-${Lowercase<First>}${CamelToKebab<Rest>}`
    : S;

type ParseUrlQuery<S extends string> =
  S extends `${infer Key}=${infer Value}&${infer Rest}`
    ? { [K in Key | keyof ParseUrlQuery<Rest>]: K extends Key ? Value : ParseUrlQuery<Rest>[K & keyof ParseUrlQuery<Rest>] }
    : S extends `${infer Key}=${infer Value}`
      ? { [K in Key]: Value }
      : {};

type CSSProperty = 'margin-top' | 'margin-bottom' | 'padding-left' | 'padding-right';
type CSSToJSStyle<S extends string> = KebabToCamel<S>;

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type ApiEndpoint = `/api/${string}`;
type RoutePattern = `${HttpMethod} ${ApiEndpoint}`;

type ParseHTTPHeader<S extends string> =
  S extends `${infer Name}: ${infer Value}`
    ? { name: Name; value: Value }
    : never;

type VersionString = `${number}.${number}.${number}`;
type ParseVersion<S extends string> =
  S extends `${infer Major}.${infer Minor}.${infer Patch}`
    ? { major: Major; minor: Minor; patch: Patch }
    : never;

模式3:遞迴型別與深度遍歷

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

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

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

type DeepMutable<T> = T extends object
  ? { -readonly [K in keyof T]: DeepMutable<T[K]> }
  : T;

type PathKeys<T, Prefix extends string = ''> = T extends object
  ? { [K in keyof T & string]: PathKeys<T[K], Prefix extends '' ? K : `${Prefix}.${K}`> }[keyof T & string]
  : Prefix;

type GetValueByPath<T, Path extends string> =
  Path extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
      ? GetValueByPath<T[Key], Rest>
      : never
    : Path extends keyof T
      ? T[Path]
      : never;

type FlattenObject<T, Prefix extends string = ''> = T extends object
  ? { [K in keyof T & string]: FlattenObject<T[K], Prefix extends '' ? K : `${Prefix}.${K}`> }[keyof T & string] extends infer U
    ? { [K in U]: U extends `${infer P}.${infer R}` ? never : U }
    : never
  : { [K in Prefix]: T };

type DeepPick<T, Paths extends readonly string[]> =
  Paths extends [infer First, ...infer Rest]
    ? First extends keyof T
      ? Rest extends string[]
        ? DeepPick<T[First], Rest> & { [K in First]: T[First] }
        : { [K in First]: T[First] }
      : {}
    : {};

type RecursiveOmit<T, K extends string> = T extends object
  ? { [P in keyof T as P extends K ? never : P]: RecursiveOmit<T[P], K> }
  : T;

type Accordion<S extends any[], T extends any[] = []> =
  T['length'] extends 10
    ? T
    : S extends [infer First, ...infer Rest]
      ? Accordion<Rest, [...T, First]>
      : T;

模式4:映射型別進階變換

type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];

type PickByValue<T, ValueType> = { [K in keyof T as T[K] extends ValueType ? K : never]: T[K] };
type OmitByValue<T, ValueType> = { [K in keyof T as T[K] extends ValueType ? never : K]: T[K] };

type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

type MutableKeys<T> = { [K in keyof T]-?: Equal<{ -readonly [P in K]: T[P] }, { [P in K]: T[P] }> extends true ? K : never }[keyof T];
type ReadonlyKeys<T> = { [K in keyof T]-?: Equal<{ -readonly [P in K]: T[P] }, { [P in K]: T[P] }> extends true ? never : K }[keyof T];

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;

type BrandKeys<T, B extends string> = { [K in keyof T]: T[K] & { __brand: B } };

type PropType<T, Path extends string> =
  Path extends `${infer K}.${infer Rest}`
    ? K extends keyof T ? PropType<T[K], Rest> : never
    : Path extends keyof T ? T[Path] : never;

interface APIResponse {
  user: {
    profile: { name: string; avatar: string };
    settings: { theme: 'dark' | 'light'; lang: string };
  };
  meta: { page: number; total: number };
}

type UserName = PropType<APIResponse, 'user.profile.name'>;
type Theme = PropType<APIResponse, 'user.settings.theme'>;

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

type TupleToUnion<T extends any[]> = T[number];

type StringKeyOf<T> = keyof T & string;

type MarkRequired<T, RK extends keyof T> = Omit<T, RK> & Required<Pick<T, RK>>;

模式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 Subtract<A extends number, B extends number> =
  BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest] ? Rest['length'] : never;

type Multiply<A extends number, B extends number, Acc extends any[] = []> =
  B extends 0 ? Acc['length']
    : Multiply<A, Subtract<B, 1>, [...Acc, ...BuildTuple<A>]>;

type Compare<A extends number, B extends number> =
  A extends B ? 0
    : BuildTuple<A> extends [...BuildTuple<B>, ...any[]] ? 1 : -1;

type IsGreaterThan<A extends number, B extends number> = Compare<A, B> extends 1 ? true : false;

type Fibonacci<N extends number> =
  N extends 0 ? 0
    : N extends 1 ? 1
      : Add<Fibonacci<Subtract<N, 1>>, Fibonacci<Subtract<N, 2>>>;

type Fib10 = Fibonacci<10>;

type Range<From extends number, To extends number, Acc extends number[] = []> =
  From extends To ? [...Acc, From]
    : Range<Add<From, 1>, To, [...Acc, From]>;

type OneToFive = Range<1, 5>;

type Divide<A extends number, B extends number, Quotient extends any[] = [], Remainder extends any[] = BuildTuple<A>> =
  B extends 0 ? never
    : Remainder extends [...BuildTuple<B>, ...infer NewRemainder]
      ? Divide<A, B, [...Quotient, unknown], NewRemainder>
      : Quotient['length'];

type Mod<A extends number, B extends number> =
  B extends 0 ? never
    : BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
      ? Mod<Rest['length'], B>
      : A;

type IsEven<N extends number> = Mod<N, 2> extends 0 ? true : false;

模式6:執行時型別安全(Zod整合)

import { z } from 'zod';

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  role: z.enum(['admin', 'editor', 'viewer']),
  tags: z.array(z.string()).default([]),
  metadata: z.record(z.unknown()).optional(),
});

type User = z.infer<typeof UserSchema>;
type UserInput = z.input<typeof UserSchema>;

const CreateUserSchema = UserSchema.omit({ id: true });
type CreateUserInput = z.infer<typeof CreateUserSchema>;

const PatchUserSchema = UserSchema.partial().omit({ id: true });
type PatchUserInput = z.infer<typeof PatchUserSchema>;

function validateOrThrow<T>(schema: z.ZodSchema<T>, data: unknown): T {
  const result = schema.safeParse(data);
  if (!result.success) {
    throw new ValidationError(result.error);
  }
  return result.data;
}

class ValidationError extends Error {
  constructor(public readonly zodError: z.ZodError) {
    super(zodError.message);
    this.name = 'ValidationError';
  }

  get fieldErrors(): Record<string, string[]> {
    return this.zodError.flatten().fieldErrors as Record<string, string[]>;
  }
}

function createTypeSafeFetcher<T>(schema: z.ZodSchema<T>) {
  return async (url: string, options?: RequestInit): Promise<T> => {
    const response = await fetch(url, options);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    const raw = await response.json();
    return validateOrThrow(schema, raw);
  };
}

const fetchUser = createTypeSafeFetcher(UserSchema);

const ApiRoutesSchema = z.union([
  z.object({ method: z.literal('GET'), path: z.string(), response: z.any() }),
  z.object({ method: z.literal('POST'), path: z.string(), body: z.any(), response: z.any() }),
  z.object({ method: z.literal('PUT'), path: z.string(), body: z.any(), response: z.any() }),
  z.object({ method: z.literal('DELETE'), path: z.string(), response: z.any() }),
]);

const EventPayloadSchema = z.discriminatedUnion('type', [
  z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
  z.object({ type: z.literal('focus'), element: z.string() }),
  z.object({ type: z.literal('keydown'), key: z.string(), code: z.string() }),
]);

type EventPayload = z.infer<typeof EventPayloadSchema>;

模式7:型別級狀態機

interface StateMachineDef {
  states: Record<string, Record<string, string>>;
}

type ValidateStateMachine<Def extends StateMachineDef> = {
  [State in keyof Def['states']]: {
    [Event in keyof Def['states'][State]]: Def['states'][State][Event] extends keyof Def['states']
      ? Def['states'][State][Event]
      : never;
  };
};

type Transition<CurrentState, Event, Def extends StateMachineDef> =
  CurrentState extends keyof Def['states']
    ? Event extends keyof Def['states'][CurrentState]
      ? Def['states'][CurrentState][Event]
      : CurrentState
    : never;

type ReachableStates<From extends string, Def extends StateMachineDef, Visited extends string = never> =
  From extends Visited
    ? never
    : From | Def['states'][From] extends infer Transitions
      ? Transitions extends Record<string, string>
        ? ReachableStates<Transitions[keyof Transitions], Def, Visited | From>
        : never
      : never;

interface OrderStateMachine extends StateMachineDef {
  states: {
    draft: { submit: 'pending_review'; discard: 'cancelled' };
    pending_review: { approve: 'approved'; reject: 'rejected'; request_changes: 'draft' };
    approved: { start: 'in_progress'; cancel: 'cancelled' };
    in_progress: { complete: 'completed'; hold: 'on_hold'; cancel: 'cancelled' };
    on_hold: { resume: 'in_progress'; cancel: 'cancelled' };
    completed: {};
    cancelled: {};
    rejected: {};
  };
}

type OrderState = keyof OrderStateMachine['states'];
type OrderEvent = keyof OrderStateMachine['states'][keyof OrderStateMachine['states']];

type AfterSubmit = Transition<'draft', 'submit', OrderStateMachine>;
type AfterApprove = Transition<'pending_review', 'approve', OrderStateMachine>;

class TypeSafeStateMachine<Def extends StateMachineDef> {
  private current: keyof Def['states'];

  constructor(
    private readonly definition: Def,
    initialState: keyof Def['states'],
  ) {
    this.current = initialState;
  }

  send<E extends string>(
    event: E & (E extends keyof Def['states'][typeof this.current] ? E : never),
  ): Transition<typeof this.current, E, Def> {
    const stateTransitions = this.definition.states[this.current as string] as Record<string, string>;
    const nextState = stateTransitions[event];
    if (!nextState) {
      throw new Error(`Invalid event "${event}" in state "${String(this.current)}"`);
    }
    this.current = nextState as keyof Def['states'];
    return nextState as Transition<typeof this.current, E, Def>;
  }

  getState(): keyof Def['states'] {
    return this.current;
  }

  canSend<E extends string>(event: E): boolean {
    const stateTransitions = this.definition.states[this.current as string] as Record<string, string>;
    return event in stateTransitions;
  }
}

const orderFsm = new TypeSafeStateMachine<OrderStateMachine>(
  { states: { draft: { submit: 'pending_review', discard: 'cancelled' }, pending_review: { approve: 'approved', reject: 'rejected', request_changes: 'draft' }, approved: { start: 'in_progress', cancel: 'cancelled' }, in_progress: { complete: 'completed', hold: 'on_hold', cancel: 'cancelled' }, on_hold: { resume: 'in_progress', cancel: 'cancelled' }, completed: {}, cancelled: {}, rejected: {} } },
  'draft',
);

避坑指南

陷阱1:分散式條件型別的意外行為

// ❌ 裸型別參數觸發分散式行為,聯合型別被展開
type Wrap<T> = T extends any ? { value: T } : never;
type Result = Wrap<string | number>; // { value: string } | { value: number }

// ✅ 用元組包裹阻止分散式行為
type WrapNoDistribute<T> = [T] extends [any] ? { value: T } : never;
type ResultNoDistribute = WrapNoDistribute<string | number>; // { value: string | number }

陷阱2:遞迴型別深度溢位

// ❌ 無限遞迴導致編譯器崩潰
type DeepFlatten<T> = T extends Array<infer U> ? DeepFlatten<U> : T;

// ✅ 限制遞迴深度
type FlattenN<T, N extends number, D extends any[] = []> =
  D['length'] extends N ? T
    : T extends Array<infer U> ? FlattenN<U, N, [...D, 1]>
    : T;

type SafeFlatten<T> = FlattenN<T, 5>;

陷阱3:infer在非條件型別中使用

// ❌ infer只能在條件型別的extends子句中使用
type BadExtract<T> = T extends Promise<infer V> ? V : infer V;

// ✅ 所有infer都必須在extends子句中宣告
type GoodExtract<T> = T extends Promise<infer V> ? V : T;

陷阱4:satisfies與型別斷言混淆

// ❌ as const丟失型別驗證
const config = { port: 3000, host: 'localhost' } as const;

// ✅ satisfies驗證型別同時保留字面量型別
const config = { port: 3000, host: 'localhost' } as const satisfies Record<string, string | number>;

陷阱5:模板字面量型別效能陷阱

// ❌ 過於寬泛的模板字面量導致編譯器嘗試大量組合
type BadRoute = `${string}/${string}/${string}/${string}`;

// ✅ 用具體聯合型別約束模板字面量
type ApiVersion = 'v1' | 'v2';
type Resource = 'users' | 'posts' | 'comments';
type GoodRoute = `/api/${ApiVersion}/${Resource}`;

報錯排查表

錯誤現象 可能原因 解決方案
Type instantiation is excessively deep 遞迴型別超過深度限制 使用計數器元組限制遞迴深度
Type produces a tuple type that is too large BuildTuple生成過大的元組 限制數學運算的範圍,避免大數運算
Expression produces a union type that is too complex 模板字面量組合爆炸 用具體聯合型別替代${string}
Type provides no match for the default export infer推導失敗 檢查條件型別的extends模式是否匹配
Circularity detected 型別循環引用 引入中間型別別名打破循環
Type 'X' is not assignable to type 'Y' 分散式條件型別意外展開 [T] extends [U]阻止分散式行為
infer declarations can only be used in extends infer用在錯誤位置 確保infer僅在條件型別的extends子句中
satisfies運算式中的型別不匹配 值與型別約束不一致 檢查satisfies右側型別是否與實際值相容
Cannot find name 'T' 泛型作用域錯誤 確保泛型參數在正確的型別別名或函式簽名中宣告
Excessive stack depth comparing types 複雜型別比較導致棧溢位 簡化型別結構,使用中間型別別名

進階最佳化

型別快取與效能

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

type Intermediate<A, B> = A extends B ? A : never;
type CachedResult<T> = Intermediate<T, string | number>;

型別級單元測試

type Assert<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;

type TestTrim = Assert<Equal<Trim<' hello '>, 'hello'>>;
type TestKebabToCamel = Assert<Equal<KebabToCamel<'my-component-name'>, 'myComponentName'>>;
type TestAdd = Assert<Equal<Add<3, 5>, 8>>;
type TestDeepPartial = Assert<Equal<keyof DeepPartial<{ a: string; b: number }>, 'a' | 'b'>>;

條件型別可讀性

type IsString<T> = T extends string ? true : false;
type IsNumber<T> = T extends number ? true : false;
type IsPrimitive<T> = IsString<T> extends true ? true : IsNumber<T> extends true ? true : false;

type DescribedConditional<T> =
  T extends string ? { type: 'string'; value: T } :
  T extends number ? { type: 'number'; value: T } :
  T extends boolean ? { type: 'boolean'; value: T } :
  { type: 'unknown'; value: T };

型別推導輔助工具

type ShowType<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type Prettify<T> = { [K in keyof T]: T[K] } & {};

對比分析

特性 TypeScript型別系統 Haskell型別系統 Rust型別系統
圖靈完備 是(遞迴型別) 是(型別族)
模式匹配 條件型別+infer 代數資料型別+模式匹配 match運算式
型別推導 區域性推導 全域性Hindley-Milner 區域性推導
高階型別 模擬(無HKT) 原生支援 不支援
執行時安全 需外部庫(Zod) 原生保證 原生保證
遞迴深度 ~1000層 無硬限制 不適用
學習曲線 中等 中等
生態成熟度 ★★★★★ ★★★ ★★★★★
實用性 ★★★★★ ★★★ ★★★★★

TypeScript模式匹配不是「型別體操」炫技,而是生產級型別安全的基石。 條件型別+infer讓你在編譯時提取和變換型別,模板字面量型別讓字串型別也能模式匹配,遞迴型別讓深度遍歷成為可能,Zod整合讓編譯時和執行時型別安全一致,型別級狀態機讓業務邏輯在型別層面可驗證。型別程式設計的最高境界不是寫出最複雜的型別,而是讓型別系統替你捕獲最多的bug


推薦工具

  • JSON格式化 — 格式化TypeScript設定和API回應JSON,快速排查型別定義問題
  • 程式碼格式化 — 格式化TypeScript程式碼,統一團隊型別定義風格
  • cURL轉程式碼 — 將API請求轉為型別安全的TypeScript fetch程式碼

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

#TypeScript 5.8#模式匹配#类型体操#条件类型#模板字面量类型#2026#前端工程