1. Partial<T>
Partial<T> 類型使類型的所有屬性成為可選的。
如果你有定義一個完整物件的接口,但有時只需要更新該物件的一部分,則 Partial<T> 是使其所有屬性成為可選的好方法。
底層原理
Partial<T> 的實現原理其實非常簡潔而優雅,它利用了 TypeScript 的 Mapped Types 和 keyof 操作符:
keyof T: 這會取得類型 T 的所有屬性名稱,並組成一個聯合類型(例如,'id' | 'name' | 'email')[P in keyof T]: 這是 Mapped Types 的語法,它會遍歷keyof T中的每一個屬性P?: T[P]: 在遍歷的過程中,它會為每個屬性P添加?符號,使其變為可選的,並且其值類型保持為T[P](即原始類型T中對應屬性P的類型)
2. Required<T>
與 Partial<T> 相反,它使某一類型的所有屬性成為必須的。
底層原理
Required<T> 的實現同樣基於 Mapped Types,它巧妙地移除了屬性上的 ? 修飾符:
keyof T: 同樣取得類型T的所有屬性名稱[P in keyof T]: 遍歷這些屬性-?: T[P]: 這裡的-符號是關鍵!它會移除屬性P上的?修飾符,使其從可選變為必須。其值類型依然保持為T[P]
3. Readonly<T>
Readonly<T> 使某個類型的所有屬性變成唯讀,初始化後無法變更它們的值。它強制執行了一種不變性(immutability),這在許多場景下都非常有利於程式碼的穩定性和安全性。
底層原理
與 Partial<T> 和 Required<T> 類似的實現,同樣利用了 Mapped Types,但是添加 readonly 修飾符:
keyof T: 取得類型T的所有屬性名稱[P in keyof T]: 遍歷這些屬性readonly T[P]: 為每個屬性P添加readonly修飾符,使其變成唯讀,同時保持其原始值類型T[P]
4. Record<K, T>
Record<K, T> 會建立一個新物件類型,其中鍵(keys)的類型由 K 指定,而值(values)的類型由 T 指定。當你需要一個類似地圖的結構來控制鍵和值時,它特別有用。
底層原理
Record<K, T> 的實現同樣運用了 Mapped Types,但它更明確地指定了鍵的類型:
K extends keyof any: 這是一個約束,表明K必須是可以用作物件鍵的類型。keyof any基本上是string | number | symbol的聯合類型。這確保了你傳給K的類型確實可以用來作為物件的鍵[P in K]: 這會遍歷類型K中的每一個成員P(例如,如果K是'admin' | 'user',那麼P會先是'admin',然後是'user'): T: 對於K中的每一個鍵P,其對應的值類型都被設定為T
5. Pick<T, K>
Pick<T, K> 透過從現有類型中選擇特定屬性來建立新物件類型。
底層原理
Pick<T, K> 的實現同樣是基於 Mapped Types 和 keyof 加上 extends 約束:
K extends keyof T: 這是一個關鍵的約束!它確保你傳入的K類型(即你想要挑選的鍵)必須是T中確實存在的屬性名稱的聯合類型。如果K包含了T中不存在的屬性,TypeScript 會直接報錯,這提供了強大的型別安全保證[P in K]: 這會遍歷K中的每一個鍵P: T[P]: 對於每個被選中的鍵P,其值類型會從原始類型T中對應的屬性T[P]複製過來
6. Omit<T, K>
它與 Pick<T, K> 的作用恰好相反,Omit<T, K> 透過從現有類型中排除特定屬性來建立新物件類型。
底層原理
Omit<T, K> 的實現其實是結合了 Pick<T, K> 和 Exclude<T, U>(下面也會提到它)。它的大致邏輯是:先取得 T 中所有屬性的鍵,然後從這些鍵中「排除」掉 K 所指定的鍵,最後再用 Pick 選取剩餘的鍵。
keyof T: 取得類型T的所有屬性名稱的聯合類型Exclude<keyof T, K>: 這會從T的所有鍵中,排除掉K中定義的鍵。例如,如果keyof T是'id' | 'name' | 'email'且K是'email',那麼Exclude的結果就是'id' | 'name'Pick<T, ...>: 最後,Pick再用Exclude運算後的結果(即剩餘的鍵)去從T中挑選出對應的屬性,形成新的類型
7. Exclude<T, U>
Exclude<T, U> 從聯合類型中刪除特定值。
底層原理
Exclude<T, U> 的實現原理其實基於 TypeScript 的條件類型(Conditional Types),這是一個非常強大的特性:
T extends U ? never : T: 這是一個條件表達式。它會對T中的每個成員進行檢查:- 如果
T的某個成員可以賦值給U(T extends U為真),那麼這個成員就會被替換成never。never是一個空類型,表示「永不存在的值」,它在聯合類型中會被自動移除。 - 如果
T的某個成員不能賦值給U(T extends U為假),那麼這個成員就會被保留下來(T)。
- 如果
- 最終,所有被替換為
never的成員都會從最終的聯合類型中被「排除」掉。
8. Extract<T, U>
Extract<T, U> 從聯合類型中提取可指派給另一種類型的特定值。
底層原理
實現原理與 Exclude<T, U> 異曲同工,同樣基於 TypeScript 的條件類型(Conditional Types),只是判斷邏輯相反:
T extends U ? T : never: 這同樣是一個條件表達式。它會對T中的每個成員進行檢查:- 如果
T的某個成員可以賦值給U(T extends U為真),那麼這個成員就會被保留下來(T) - 如果
T的某個成員不能賦值給U(T extends U為假),那麼這個成員就會被替換成never
- 如果
- 最終,所有被替換為
never的成員都會從最終的聯合類型中被「移除」,只留下符合條件的成員
Pick / Omit 和 Extract / Exclude 這兩組工具類型之間的核心差異
它們的主要差異就在於它們操作的「目標類型」不同:
Pick<T, K> 和 Omit<T, K>:
- 操作目標: 主要針對物件類型(Object Types)或接口(Interfaces) 的屬性進行操作
- 操作方式:
Pick是選擇物件中的特定屬性,而Omit是排除物件中的特定屬性 - 類型
K: 傳入的K參數必須是該物件類型T中存在的屬性名稱(keyof T)
Extract<T, U> 和 Exclude<T, U>:
- 操作目標: 專門針對聯合類型(Union Types) 的成員進行操作
- 操作方式:
Extract是從聯合類型中「提取」出符合條件的成員,而Exclude是從聯合類型中「排除」不符合條件的成員 - 類型
U: 傳入的U參數是與T中的成員進行「可賦值性」比較,以決定是保留還是排除
| 特性 | Pick<T, K> & Omit<T, K> | Extract<T, U> & Exclude<T, U> |
|---|---|---|
| 操作目標 | 物件類型 (Object Types / Interfaces) | 聯合類型 (Union Types) |
| 操作對象 | 物件的屬性 (Properties) | 聯合類型的成員 (Members) |
| K / U 參數 | K 必須是 T 中存在的屬性鍵 (keyof T) | U 是與 T 中成員進行可賦值性比較的類型 |
| 目標 | 建立具有/不具有特定屬性的新物件類型 | 建立具有/不具有特定成員的新聯合類型 |
| 思考角度 | 關於物件「結構」的增減 | 關於聯合類型「成員」的篩選 |
9. NonNullable<T>
NonNullable<T> 從類型中刪除 null 和 undefined。
底層原理
NonNullable<T> 的實現非常簡潔,它正是利用了上面提到的 Exclude<T, U>:
它基本上就是說:「從類型 T 中,排除掉 null 和 undefined。」
10. ReturnType<T>
ReturnType<T> 提取函數的回傳類型。
底層原理 (Under the Hood):
ReturnType<T> 的實現同樣依賴於 TypeScript 的條件類型(Conditional Types) 和型別推斷(infer 關鍵字):
T extends (...args: any) => any: 這是一個約束,確保T必須是一個函式類型。(...args: any) => any表示接受任意參數並回傳任意值的函式T extends (...args: any) => infer R ? R : any: 這是條件類型的主體:- 如果函式類型
T可以賦值給(...args: any) => infer R(意思是T確實是一個函式,並且它的回傳類型可以被推斷為R),那麼就回傳這個被推斷出來的類型R - 否則(如果
T不是一個函式類型),就回傳any
- 如果函式類型
