Implement the built-in Omit<T, K> generic without using it.
Constructs a type by picking all properties from T and then removing K
實現內建的 Omit<T, K>
泛型,不使用內建版本。
透過選取 T
中的所有屬性,然後移除 K
,來構造一個型別。
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
type a = MyReturnType<typeof fn> // should be "1 | 2"
接下來,你的任務是讓下面的type cases測試通過:
type cases = [
Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,
Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
Expect<Equal<Expected3, MyOmit<Todo1, 'description' | 'completed'>>>,
]
interface Todo {
title: string
description: string
completed: boolean
}
interface Todo1 {
readonly title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
completed: boolean
}
interface Expected2 {
title: string
}
interface Expected3 {
readonly title: string
}
從以下幾個方向來思考:
推導返回型別 (Inferring the Return Type): 我們的目標是從類型 T
中挑出屬性,並排除 K
。可以使用條件型別搭配 keyof
來推導出結果型別。
條件型別與映射型別 (Conditional and Mapped Types): 你會需要將條件型別與映射型別結合,來過濾屬性並構造新的型別。
泛型 (Generics): 設定 T
為泛型,並且將 K
作為需要被移除的屬性集。在構建映射型別時,確保靈活排除這些屬性。
解法1:
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K? never:P]:T[P]
}
細節分析:
K extends keyof T
:K
是要移除的屬性,並且必須是 T
的屬性鍵之一。這樣的限制可以確保只會移除有效的屬性,避免處理不存在的屬性。P extends K ? never
: P
來判斷 P
是否屬於 K
。如果屬於,就會返回 never,從而排除該屬性;如果不屬於,則保留屬性 P
。這樣可以靈活地移除指定的屬性,保留其他屬性。[P in keyof T]
是 TypeScript 中的映射型別語法,用來迭代 T
的所有屬性。結合條件型別,它能實現根據 K
排除指定屬性的功能。[P in keyof T as P extends K ? never : P]
中的 as
語法提供了一個條件過濾機制,用來判斷哪些屬性應該被保留。當條件成立時,它會讓屬性名被過濾掉 (never),否則屬性名會保留。這是 TypeScript 提供的一種靈活的方式來映射型別。解法2:
type MyOmit<T, K extends keyof T> = {
[P in keyof T as Exclude<P, K>]: T[P]
}
細節分析:
條件型別與工具型別 (Conditional Types and Utility Types):
使用工具型別 Exclude<P, K>
,該工具型別會將 P
從 K
中排除。與 P extends K ? never : P
的邏輯相似,但 Exclude 是內建的工具型別,更加簡潔明瞭。
這樣,我們就能順利通過測試啦 🎉 😭 🎉
Mapped Types (Key Remapping via as
) - 未來補充 😭
本次介紹了 Omit
的實作,下一關會挑戰 Readonly
,期待再相見!