內建工具類型實作原理
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];
// string7.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→ 提取類型
進階挑戰
- 實作
DeepReadonly<T>:深度唯讀 - 實作
PickByType<T, U>:選取特定類型的屬性 - 實作
OptionalExcept<T, K>:除了 K 以外都可選
typescript
// 練習 2 提示
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};