跳轉到內容

TypeScript 高級類型與實戰技巧 2026

TypeScript 高級類型

🔷 類型是最好的文檔。 TypeScript 的高級類型系統是其最強大的特性之一。掌握條件類型、映射類型、模板字面量類型等"類型體操"技巧,可以讓你在編譯階段就消除大量 Bug,寫出更健壯的代碼。本文帶你從進階到精通 TypeScript 類型系統。

本文將帶你係統掌握:

  • ✅ 條件類型與分佈式條件類型
  • ✅ 映射類型與鍵重映射
  • ✅ 模板字面量類型
  • ✅ infer 關鍵字與類型提取
  • ✅ 類型守衛與類型收窄
  • ✅ 實用類型體操技巧
  • ✅ 類型編程實戰案例
  • ✅ 類型性能優化

一、條件類型

1.1 基本語法

條件類型就像類型層面的三元運算符:

typescript
T extends U ? X : Y

示例:

typescript
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;   // true
type B = IsString<42>;        // false
type C = IsString<string>;   // true

1.2 實用條件類型

typescript
// 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;

// 提取函數返回類型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取 Promise 返回類型
type Awaited<T> = T extends Promise<infer U> ? U : T;

// 提取數組元素類型
type ElementOf<T> = T extends (infer E)[] ? E : never;

type R1 = ElementOf<string[]>;           // string
type R2 = ElementOf<number[]>;           // number
type R3 = Awaited<Promise<boolean>>;     // boolean

1.3 分佈式條件類型

T 是聯合類型時,條件類型會分佈式地應用於每個成員:

typescript
type ToArray<T> = T extends any ? T[] : never;

// 分佈式:聯合類型會被拆開
type R = ToArray<string | number>;  
// string[] | number[]

// 阻止分佈:用方括號包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type R2 = ToArrayNonDist<string | number>;  
// (string | number)[]

實戰:排除類型

typescript
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

type T1 = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"
type T2 = Extract<"a" | "b" | "c", "a" | "b">;  // "a" | "b"

二、infer 關鍵字

2.1 基本用法

infer 用於在條件類型中聲明並推斷類型變量:

typescript
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type R = GetReturnType<() => string>;  // string

2.2 提取函數參數類型

typescript
// 獲取第一個參數類型
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

// 獲取所有參數類型
type Params<T> = T extends (...args: infer P) => any ? P : never;

type P1 = FirstParam<(name: string, age: number) => void>;  // string
type P2 = Params<(name: string, age: number) => void>;       // [string, number]

2.3 提取構造函數類型

typescript
type InstanceType<T> = T extends new (...args: any[]) => infer I ? I : never;

class Person {}
type P = InstanceType<typeof Person>;  // Person

2.4 提取數組首尾元素

typescript
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type F = First<[1, 2, 3]>;  // 1
type L = Last<[1, 2, 3]>;   // 3

2.5 提取 Promise 鏈

typescript
// 遞歸解包嵌套 Promise
type UnwrapPromise<T> = T extends Promise<infer U> 
  ? U extends Promise<any> 
    ? UnwrapPromise<U> 
    : U 
  : T;

type R = UnwrapPromise<Promise<Promise<Promise<number>>>>;  // number

三、映射類型

3.1 基本映射

映射類型可以基於舊類型創建新類型:

typescript
// 所有屬性變為只讀
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

// 所有屬性變為可空
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

3.2 鍵重映射(Key Remapping)

TypeScript 4.1+ 支持在映射類型中重新映射鍵:

typescript
// 將所有鍵添加前綴
type AddPrefix<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};

interface User {
  name: string;
  age: number;
}

type PrefixedUser = AddPrefix<User, "user">;
// { userName: string; userAge: number }

3.3 過濾屬性

typescript
// 只保留函數屬性
type FunctionProperties<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

// 只保留非函數屬性
type NonFunctionProperties<T> = {
  [K in keyof T as T[K] extends Function ? never : K]: T[K];
};

class MyClass {
  name = "test";
  age = 18;
  greet() {}
}

type Data = NonFunctionProperties<MyClass>;  // { name: string; age: number }

3.4 修改屬性修飾符

typescript
// 移除 readonly
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

// 移除可選
type Required<T> = {
  [P in keyof T]-?: T[P];
};

四、模板字面量類型

4.1 基本用法

typescript
type Greeting = `hello ${string}`;

const g1: Greeting = "hello world";     // OK
const g2: Greeting = "hello typescript"; // OK

4.2 聯合類型組合

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

type Style = `${Size}-${Color}`;
// "small-red" | "small-blue" | "small-green" | "medium-red" | ...

4.3 方法名生成

typescript
type Fetcher = {
  [K in keyof API as `fetch${Capitalize<string & K>}`]: () => Promise<API[K]>;
};

interface API {
  user: User;
  post: Post;
}

// 生成: fetchUser(): Promise<User> 和 fetchPost(): Promise<Post>

4.4 路徑類型

typescript
type Path<T, P extends string = ""> = 
  T extends object 
    ? {
        [K in keyof T & string]: Path<T[K], `${P}.${K}`>;
      }[keyof T & string]
    : P;

interface Config {
  db: { host: string; port: number };
  redis: { url: string };
}

type ConfigPaths = Path<Config>;
// "db.host" | "db.port" | "redis.url"

五、類型守衛

5.1 typeof 守衛

typescript
function process(value: string | number) {
  if (typeof value === "string") {
    value.toUpperCase();  // 類型收窄為 string
  } else {
    value.toFixed(2);     // 類型收窄為 number
  }
}

5.2 instanceof 守衛

typescript
class NetworkError extends Error {
  constructor(public statusCode: number) {
    super();
  }
}

function handleError(err: Error | NetworkError) {
  if (err instanceof NetworkError) {
    console.log(err.statusCode);  // 類型收窄為 NetworkError
  }
}

5.3 in 守衛

typescript
interface Fish { swim: () => void }
interface Bird { fly: () => void }

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim();  // 類型收窄為 Fish
  } else {
    animal.fly();   // 類型收窄為 Bird
  }
}

5.4 自定義類型守衛

typescript
function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(v => typeof v === "string");
}

function isError(value: unknown): value is Error {
  return value instanceof Error;
}

// 使用
function handle(data: unknown) {
  if (isStringArray(data)) {
    data.forEach(s => s.toUpperCase());  // 類型為 string[]
  }
}

5.5 斷言函數

typescript
function assertDefined<T>(value: T | undefined | null): asserts value is T {
  if (value === undefined || value === null) {
    throw new Error(`Expected value to be defined, got ${value}`);
  }
}

function processUser(user: User | undefined) {
  assertDefined(user);
  user.name;  // 類型收窄為 User
}

六、實用類型體操

6.1 DeepPartial

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

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

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

6.2 DeepReadonly

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

6.3 Omit 與 Pick 組合

typescript
// 按類型選取屬性
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface User {
  name: string;
  age: number;
  email: string;
  active: boolean;
}

type StringProps = PickByType<User, string>;
// { name: string; email: string }

type NumberProps = PickByType<User, number>;
// { age: number }

6.4 聯合類型轉交叉類型

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

type R = UnionToIntersection<{ a: 1 } | { b: 2 }>;
// { a: 1 } & { b: 2 }

6.5 獲取對象路徑類型

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

interface Config {
  db: { host: string; port: number };
}

type HostType = Get<Config, "db.host">;  // string
type PortType = Get<Config, "db.port">;   // number

七、類型編程實戰

7.1 類型安全的 EventEmitter

typescript
type EventMap = Record<string, any>;

class TypedEmitter<Events extends EventMap> {
  private listeners = new Map<keyof Events, Set<Function>>();

  on<K extends keyof Events>(event: K, fn: (payload: Events[K]) => void) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(fn);
    return this;
  }

  emit<K extends keyof Events>(event: K, payload: Events[K]) {
    this.listeners.get(event)?.forEach(fn => fn(payload));
  }

  off<K extends keyof Events>(event: K, fn: Function) {
    this.listeners.get(event)?.delete(fn);
  }
}

// 使用
interface MyEvents {
  open: { url: string };
  close: { code: number };
}

const emitter = new TypedEmitter<MyEvents>();
emitter.on("open", (e) => e.url);      // e 類型: { url: string }
emitter.emit("close", { code: 100 });  // OK
// emitter.emit("close", { wrong: 1 })  // 編譯錯誤!

7.2 類型安全的路由參數

typescript
type ExtractParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractParams<Rest>
    : T extends `${string}:${infer Param}`
      ? { [K in Param]: string }
      : {};

// 使用
type Params = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }

function route<T extends string>(
  path: T,
  params: ExtractParams<T>
): string {
  let result = path;
  for (const [key, value] of Object.entries(params)) {
    result = result.replace(`:${key}`, value);
  }
  return result;
}

route("/users/:userId/posts/:postId", { userId: "1", postId: "2" });  // OK
// route("/users/:userId", {})  // 編譯錯誤!缺少 userId

7.3 類型安全的狀態機

typescript
type Transition<S, M> = {
  [K in keyof M]: K extends keyof S ? M[K] : never;
};

interface States {
  idle: { type: "idle" };
  loading: { type: "loading"; requestId: string };
  success: { type: "success"; data: string };
  error: { type: "error"; message: string };
}

type State = States[keyof States];
// { type: "idle" } | { type: "loading"; requestId: string } | ...

type Transitions = {
  idle: "loading";
  loading: "success" | "error";
  success: "idle";
  error: "idle";
};

function transition(
  current: State,
  next: Transitions[State["type"]]
): State {
  // 狀態轉換邏輯
  return { type: next } as State;
}

八、類型性能優化

8.1 避免過度遞歸

typescript
// 慢:遞歸深度大
type DeepPartialSlow<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartialSlow<T[P]> : T[P];
};

// 快:限制遞歸深度
type DeepPartial<T, Depth extends number = 3> = {
  [P in keyof T]?: Depth extends 0
    ? T[P]
    : T[P] extends object
      ? DeepPartial<T[P], [...Tuple<Depth>, 0]["length"]>
      : T[P];
};

8.2 使用 satisfies 替代直接斷言

typescript
// TypeScript 4.9+
const config = {
  port: 3000,
  host: "localhost",
} satisfies Record<string, string | number>;
// config 推斷為 { port: number; host: string },而不是 Record<string, string | number>

8.3 避免大聯合類型

typescript
// 不推薦:大聯合類型導致編譯慢
type Status = "idle" | "loading" | "success" | "error" | "timeout" | "offline" | "connecting" | "disconnected" | "paused" | "resuming";

// 推薦:使用枚舉或常量對象
const Status = {
  IDLE: "idle",
  LOADING: "loading",
  SUCCESS: "success",
  ERROR: "error",
} as const;

type Status = typeof Status[keyof typeof Status];

九、常用工具類型速查

typescript
// 內置工具類型
Partial<T>           // 所有屬性可選
Required<T>          // 所有屬性必填
Readonly<T>          // 所有屬性只讀
Pick<T, K>           // 選取部分屬性
Omit<T, K>           // 排除部分屬性
Record<K, V>         // 鍵值對類型
ReturnType<T>        // 函數返回類型
Parameters<T>        // 函數參數類型
InstanceType<T>      // 構造函數實例類型
NonNullable<T>       // 排除 null 和 undefined
Awaited<T>           // 解包 Promise
Uppercase<S>         // 字符串大寫
Lowercase<S>         // 字符串小寫
Capitalize<S>        // 首字母大寫
Uncapitalize<S>     // 首字母小寫

// 常用自定義工具類型
DeepPartial<T>       // 深層可選
DeepReadonly<T>      // 深層只讀
PickByType<T, U>     // 按類型選取
Mutable<T>           // 移除只讀
Optional<T, K>       // 指定屬性可選

十、總結

TypeScript 高級類型是構建大型應用的核心能力:

  1. 條件類型是類型層面的 if-else
  2. infer 讓你從已有類型中提取信息
  3. 映射類型讓你變換和重塑類型
  4. 模板字面量類型讓字符串類型編程成為可能
  5. 類型守衛讓運行時類型檢查與編譯時類型安全無縫銜接
  6. 類型體操解決實際問題的利器,但要避免過度複雜

🔷 類型安全的代碼是最好的代碼。掌握 TypeScript 高級類型,讓你在編譯階段就消滅 Bug。


相關文章推薦:

最後更新於: