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
- 如果函式類型