目標讀者:已經熟悉 TypeScript 基礎語法、泛型、條件類型的同學。本文按常見工具類型的分類與順序實現并解釋
Partial
、Required
、Readonly
、Pick
、Omit
、Record
、Exclude
、Extract
、NonNullable
、ReturnType
、Parameters
、ConstructorParameters
、InstanceType
、ThisParameterType
、OmitThisParameter
。
目錄(與上一篇順序一致)
- Partial
- Required
- Readonly
- Pick
- Omit
- Record
- Exclude
- Extract
- NonNullable
- ReturnType
- Parameters
- ConstructorParameters
- InstanceType
- ThisParameterType
- OmitThisParameter
注意:以下實現主要用于教學與閱讀(與 TypeScript 官方實現甚為相近),可幫助理解背后的類型技巧(映射類型、條件類型、
infer
、keyof
等)。
1. Partial<T>
用途回顧:把類型 T
的所有屬性變為可選。
實現思路:使用映射類型把每個屬性的修飾符 ?
添加上。
type MyPartial<T> = {[K in keyof T]?: T[K];
};// 示例
interface User { id: number; name: string }
type PUser = MyPartial<User>; // { id?: number; name?: string }
要點:[K in keyof T]
遍歷 T
的所有鍵,?:
表示可選屬性。
2. Required<T>
用途回顧:把 T
的所有屬性變為必選。
實現思路:與 Partial
相反,移除可選修飾符 ?
。
type MyRequired<T> = {[K in keyof T]-?: T[K];
};// 示例
interface Opt { a?: number }
type R = MyRequired<Opt>; // { a: number }
要點:-?
是映射類型的語法,用來移除可選標記。
3. Readonly<T>
用途回顧:把 T
的所有屬性變為只讀。
實現思路:使用映射類型并加上 readonly
修飾符。
type MyReadonly<T> = {readonly [K in keyof T]: T[K];
};// 示例
type R = MyReadonly<{ x: number }>; // { readonly x: number }
要點:readonly
與 ?
、-?
一樣都是映射類型可用的修飾符。
4. Pick<T, K>
用途回顧:從 T
中挑選一部分屬性 K
(K
是鍵的聯合類型)。
實現思路:遍歷 K
(extends keyof T
),并把對應屬性取出。
type MyPick<T, K extends keyof T> = {[P in K]: T[P];
};// 示例
interface User { id: number; name: string; age: number }
type Preview = MyPick<User, 'id' | 'name'>; // { id: number; name: string }
要點:K extends keyof T
約束保證 K
只包含 T
的鍵。
5. Omit<T, K>
用途回顧:從 T
中排除某些屬性 K
。
實現思路:Omit<T, K>
通常等價于從 T
的鍵中 Exclude<keyof T, K>
,然后 Pick
出剩余的鍵。
type MyOmit<T, K extends keyof any> = MyPick<T, Exclude<keyof T, K>>;// 或者直接寫成:
// type MyOmit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P] }// 示例
interface User { id: number; name: string; password: string }
type Safe = MyOmit<User, 'password'>; // { id: number; name: string }
要點:K extends keyof any
(或 keyof T
)使得 K
可以是字符串字面量等;Exclude
在后面會解釋。
6. Record<K, T>
用途回顧:構造一個以聯合類型 K
為鍵,值為 T
的對象類型。
實現思路:映射類型直接遍歷 K
。
type MyRecord<K extends keyof any, T> = {[P in K]: T;
};// 示例
type Roles = 'admin' | 'user';
type RoleCount = MyRecord<Roles, number>; // { admin: number; user: number }
要點:keyof any
表示允許任意作為 object key 的類型(string | number | symbol
)。
7. Exclude<T, U>
用途回顧:從聯合類型 T
中排除能賦值給 U
的成員。
實現思路:Exclude
是分布式條件類型(conditional type)的一種應用:
// 分布式條件類型:當 T 是聯合類型時,條件類型會對聯合的每個成員逐一計算
// 例如: T = A | B, 則 T extends U ? X : Y 會計算 A extends U ? X : Y 以及 B extends U ? X : Y,然后把結果聯合在一起type MyExclude<T, U> = T extends U ? never : T;// 示例
type E = MyExclude<'a' | 'b' | 'c', 'a' | 'f'>; // 'b' | 'c'
要點:Exclude
利用了條件類型的“分布式”特性:當 T
是聯合類型時,T extends ...
會分解處理每個成員。
8. Extract<T, U>
用途回顧:從 T
中提取可賦值給 U
的成員(交集)。
實現思路:和 Exclude
相反:
type MyExtract<T, U> = T extends U ? T : never;// 示例
type X = MyExtract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'
要點:同樣利用了條件類型的分布式行為。
9. NonNullable<T>
用途回顧:移除 null
和 undefined
。
實現思路:等價于 Exclude<T, null | undefined>
。
type MyNonNullable<T> = MyExclude<T, null | undefined>;// 示例
type N = MyNonNullable<string | null | undefined>; // string
要點:這是組合前面工具類型的好例子。
10. ReturnType<T>
用途回顧:獲取函數類型 T
的返回值類型。
實現思路:使用 infer
在條件類型中提取返回類型。
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;// 示例
function foo() { return { x: 1 } }
type FooRet = MyReturnType<typeof foo>; // { x: number }
要點:infer R
用來聲明并捕獲返回類型。
11. Parameters<T>
用途回顧:獲取函數類型 T
的參數類型元組。
實現思路:同樣用 infer
提取參數元組 P
。
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;function greet(a: string, b: number) {}
type G = MyParameters<typeof greet>; // [string, number]
要點:通過 infer P
捕獲參數列表的類型元組。
12. ConstructorParameters<T>
用途回顧:獲取構造函數類型(類/構造簽名)的參數元組。
實現思路:這里 T
是 new (...args: any) => any
的構造簽名;用 infer
提取構造參數。
type MyConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;class Person { constructor(name: string, age: number) {} }
type C = MyConstructorParameters<typeof Person>; // [string, number]
要點:abstract new
是為了兼容普通類與抽象構造簽名。
13. InstanceType<T>
用途回顧:給定一個構造函數類型 T
,返回其實例類型。
實現思路:使用條件類型匹配 new (...args: any) => infer R
,返回實例 R
。
type MyInstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;class Person { name = 'tom' }
type P = MyInstanceType<typeof Person>; // Person
要點:InstanceType
常用于庫設計或工廠模式中從類類型推導實例類型。
14. ThisParameterType<T>
用途回顧:提取函數類型中的 this
參數類型(如果有)。
實現思路:匹配 this: X
形式的函數簽名并用 infer
捕獲 X
。
type MyThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;function fn(this: { name: string }, a: number) { return this.name }
type ThisT = MyThisParameterType<typeof fn>; // { name: string }
要點:如果函數沒有 this
參數,官方實現會返回 unknown
。
15. OmitThisParameter<T>
用途回顧:移除函數類型中的 this
參數,得到普通函數類型(用于 bind
/call
時的 type convenience)。
實現思路:如果函數包含 this
參數,將其轉換為不含 this
的函數類型。
type MyOmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (this: any, ...args: infer P) => infer R ? (...args: P) => R : T;// 示例
function say(this: { name: string }, n: number) { return this.name + n }
type FnNoThis = MyOmitThisParameter<typeof say>; // (n: number) => string
要點:實現里先檢查 ThisParameterType<T>
是否為 unknown
(即函數沒有顯式 this
),如果是就直接返回原類型 T
;否則提取參數與返回值并重構為不帶 this
的函數類型。
額外:官方實現 vs 教學實現差異
- 官方的實現會有更多兼容性考量、
any
/unknown
微妙行為處理,以及對 TS 版本特性的更細致支持(例如abstract new
、this
分支的邊界情況)。 - 教學實現避免極端兼容性,為了可讀性而做了簡化,但核心思想一致。
小結
通過實現這些常用工具類型,你可以更清楚地理解:
- 映射類型(
[K in keyof T]
)是如何構造新類型的; - 條件類型與其分布式特性如何在聯合類型上逐項計算;
infer
如何在條件類型中提取內部類型(函數參數、返回值、Promise 的包裹類型等)。
掌握這些技巧之后,你可以讀懂并自己實現更復雜的工具類型,寫出更類型安全、可復用的代碼庫。