跳至主要內容
Skip to content

內建工具類型實作原理

TypeScript 內建了許多實用的工具類型。本篇將深入解析它們的實作原理,讓你掌握類型運算的核心技巧。


一、 Partial 與 Required

1.1 Partial

將所有屬性變為可選:

typescript
// 內建實作
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 使用範例
interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = Partial<User>;
// { id?: number, name?: string, email?: string }

1.2 Required

將所有屬性變為必填:

typescript
// 內建實作
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// 使用範例
interface Config {
  host?: string;
  port?: number;
}

type RequiredConfig = Required<Config>;
// { host: string, port: number }

> `-?` 表示「移除可選修飾符」,對應 `+?` 表示「添加」(可省略 `+`)。


二、 Readonly

2.1 Readonly

將所有屬性變為唯讀:

typescript
// 內建實作
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

// 使用範例
interface User {
  id: number;
  name: string;
}

type ReadonlyUser = Readonly<User>;
// { readonly id: number, readonly name: string }

const user: ReadonlyUser = { id: 1, name: 'John' };
// user.name = 'Jane'  // 錯誤!唯讀

2.2 移除 Readonly

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

type MutableUser = Mutable<ReadonlyUser>;
// { id: number, name: string }

三、 Pick 與 Omit

3.1 Pick

選取特定屬性:

typescript
// 內建實作
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// 使用範例
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// { id: number, name: string, email: string }

3.2 Omit

排除特定屬性:

typescript
// 內建實作
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// 拆解:
// 1. keyof T = 'id' | 'name' | 'email' | 'password'
// 2. Exclude<keyof T, 'password'> = 'id' | 'name' | 'email'
// 3. Pick<T, 'id' | 'name' | 'email'>

// 使用範例
type PublicUser = Omit<User, 'password'>;
// { id: number, name: string, email: string }

四、 Record

4.1 基本實作

建立鍵值對物件類型:

typescript
// 內建實作
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

// keyof any = string | number | symbol

// 使用範例
type UserRoles = Record<'admin' | 'user' | 'guest', boolean>;
// { admin: boolean, user: boolean, guest: boolean }

type StringMap = Record<string, string>;
// { [key: string]: string }

4.2 實用範例

typescript
// 權限表
type Permissions = Record<string, boolean>;

const permissions: Permissions = {
  read: true,
  write: false,
  delete: false,
};

// 狀態映射
type Status = 'idle' | 'loading' | 'success' | 'error';
type StatusMessages = Record<Status, string>;

const messages: StatusMessages = {
  idle: '等待中',
  loading: '載入中...',
  success: '成功!',
  error: '發生錯誤',
};

五、 Exclude 與 Extract

5.1 Exclude

從聯合類型中排除:

typescript
// 內建實作
type Exclude<T, U> = T extends U ? never : T;

// 原理:分發條件類型
// Exclude<'a' | 'b' | 'c', 'a'>
// = ('a' extends 'a' ? never : 'a') | ('b' extends 'a' ? never : 'b') | ('c' extends 'a' ? never : 'c')
// = never | 'b' | 'c'
// = 'b' | 'c'

// 使用範例
type T = Exclude<'a' | 'b' | 'c', 'a' | 'b'>;
// 'c'

5.2 Extract

從聯合類型中提取:

typescript
// 內建實作
type Extract<T, U> = T extends U ? T : never;

// 使用範例
type T = Extract<'a' | 'b' | 'c', 'a' | 'd'>;
// 'a'

type Functions = Extract<string | number | (() => void), Function>;
// () => void

六、 NonNullable

6.1 基本實作

移除 null 和 undefined:

typescript
// 內建實作
type NonNullable<T> = T & {};

// 舊版實作
type NonNullable<T> = T extends null | undefined ? never : T;

// 使用範例
type T = NonNullable<string | null | undefined>;
// string

七、 Parameters 與 ReturnType

7.1 Parameters

取得函式參數類型:

typescript
// 內建實作
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

// 使用範例
function greet(name: string, age: number): void {}

type GreetParams = Parameters<typeof greet>;
// [name: string, age: number]

type FirstParam = Parameters<typeof greet>[0];
// string

7.2 ReturnType

取得函式回傳類型:

typescript
// 內建實作
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;

// 使用範例
function createUser(): { id: number; name: string } {
  return { id: 1, name: 'John' };
}

type User = ReturnType<typeof createUser>;
// { id: number, name: string }

八、 ConstructorParameters 與 InstanceType

8.1 ConstructorParameters

取得建構函式參數類型:

typescript
// 內建實作
type ConstructorParameters<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: infer P) => any ? P : never;

// 使用範例
class User {
  constructor(public name: string, public age: number) {}
}

type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]

8.2 InstanceType

取得類別實例類型:

typescript
// 內建實作
type InstanceType<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: any) => infer R ? R : any;

// 使用範例
type UserInstance = InstanceType<typeof User>;
// User

九、 Awaited

9.1 基本實作

遞迴解包 Promise:

typescript
// 內建實作(簡化版)
type Awaited<T> = T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F): any }
  ? F extends (value: infer V) => any
    ? Awaited<V>
    : never
  : T;

// 使用範例
type A = Awaited<Promise<string>>;
// string

type B = Awaited<Promise<Promise<number>>>;
// number(遞迴解包)

type C = Awaited<string>;
// string(非 Promise 直接回傳)

十、 字串操作類型

10.1 內建類型

typescript
// 轉大寫
type Uppercase<S extends string> = intrinsic;

// 轉小寫
type Lowercase<S extends string> = intrinsic;

// 首字母大寫
type Capitalize<S extends string> = intrinsic;

// 首字母小寫
type Uncapitalize<S extends string> = intrinsic;

// 使用範例
type A = Uppercase<'hello'>; // 'HELLO'
type B = Lowercase<'HELLO'>; // 'hello'
type C = Capitalize<'hello'>; // 'Hello'
type D = Uncapitalize<'Hello'>; // 'hello'

> `intrinsic` 表示這些類型由 TypeScript 編譯器內部實作。


總結

工具類型用途核心技巧
Partial全部可選映射 + ?
Required全部必填映射 + -?
Readonly全部唯讀映射 + readonly
Pick選取屬性映射 + in
Omit排除屬性Pick + Exclude
Record建立物件映射
Exclude排除聯合條件類型
Extract提取聯合條件類型
Parameters函式參數infer
ReturnType函式回傳infer

> **記憶技巧**:

  • 映射類型 → 修改物件
  • 條件類型 → 過濾聯合
  • infer → 提取類型

進階挑戰

  1. 實作 DeepReadonly<T>:深度唯讀
  2. 實作 PickByType<T, U>:選取特定類型的屬性
  3. 實作 OptionalExcept<T, K>:除了 K 以外都可選
typescript
// 練習 2 提示
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

延伸閱讀與資源