在 TypeScript 中,內(nèi)置工具類型(utility types)是一組預定義的類型字旭,用于在類型層面上進行各種操作对湃。對于 ts 開發(fā)者來說,開始使用這類工具是一個走出新手村的重要標志遗淳。截止至 2024 年 8 月拍柒,ts 官方共提供了 22 個內(nèi)置的工具類型。大家可以在官網(wǎng)查看具體的文檔屈暗。當然拆讯,本文并不是來集中介紹這些類型的用法,我們要更近一步养叛,來看看如何用更底層的類型方法來實現(xiàn)這些工具類型种呐。
Record
我們先從最簡單的入手
Record<K, T>
將 K
中的每個屬性值轉(zhuǎn)化為 T
類型,例如:
type Animal = 'Dog' | 'Cat';
type AnimalRecord = Record<Animal, string>;
// type AnimalRecord = {
// Dog: string;
// Cat: string;
// }
Record 的實現(xiàn)如下:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type K = keyof any; // string | number | symbol
Record 是最最常用的一個工具類型弃甥,實現(xiàn)也極其簡單爽室,只需要用到我們在上期中介紹的類型映射。簡單遍歷第一個泛型 K 的每一個屬性潘飘,并將屬性值都轉(zhuǎn)成第二個泛型 T 類型肮之。 這里對 K 做了限制,就是它只能是 string卜录、 number 和 symbol 的一種。我們再簡單展開一下眶明, keyof any
等價于聯(lián)合類型string | number | symbol
艰毒;如果是老手,你可能還會知道 ts 定義了一個原生類型 type PropertyKey = string | number | symbol
搜囱,有時候偷個懶不想寫一大串string | number | symbol
丑瞧,可以直接使用PropertyKey
秀一把柑土。
Partial & Required & Readonly
Partial<T>
: 將 T
的所有屬性變?yōu)榭蛇x,例如:
type Vegetable = {
Onion: string;
Garlic: number;
};
type PartialVegetable = Partial<Vegetable>;
// type PartialVegetable = {
// Onion?: string;
// Garlic?: number;
// }
Partial 的實現(xiàn)如下:
type Partial<T> = {
[P in keyof T]?: T[P];
};
這里有個知識點: 在冒號前加個 ?
(等價于+?
)就表示該鍵值的類型是可選類型(即有可能是 undefined).
Required<T>
: 把所有屬性變成必選
有 +?
操作绊汹,自然也有 -?
稽屏,Required 就是Partial的反向操作:
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Vegetable = {
Onion?: string;
Garlic西乖?: number;
};
type RequiredVegetable = Required<Vegetable>;
// type RequiredVegetable = {
// Onion: string;
// Garlic: number;
// }
Readonly<T>
: 將所有屬性變成只讀
類似加減 ?
的操作還有一個就是:加減 readonly
狐榔,只不過 readonly
要放在屬性的最前面。
再看看 Readonly
的實現(xiàn)(這里readonly
等價于+readonly
):
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
正好获雕,我們再做個練習題: Mutable
實現(xiàn)通用的
Mutable<T>
, 使得T
中的所有屬性都是可變的(不是只讀的)
interface Todo {
readonly title: string;
readonly completed: boolean;
}
type MutableTodo = Mutable<Todo>; // { title: string; completed: boolean; }
很簡單薄腻,-readonly
就行
type Mutable<T extends object> = {
-readonly [K in keyof T]: T[K];
};
Exclude & Extract & Pick & Omit
我們稍增加一點難度,實現(xiàn)一些有兩個泛型的類型
Exclude<T, U>
: 從T
中剔除那些可賦值給U
的類型
Exclude主要用戶聯(lián)合類型的造作届案。如下所示從聯(lián)合類型 a' | 'b'
中剔除c
( c
是 'a' | 'c'
的子集 ) 得到 b
type C = Exclude<'a' | 'b', 'a' | 'c'>; // 'b'
答案很簡單直接用 extends 判斷就行了:
type Exclude<T, U> = T extends U ? never : T;
不過這里要補充個 extends 的知識點庵楷,
T extends U ? never : T
實際執(zhí)行時是對聯(lián)合類型T
里的每一個元素分別進行條件判斷,然后對每一個條件判斷的結(jié)果再組裝成新的聯(lián)合類型楣颠。以 Exclude<'a' | 'b', 'a' | 'c'>
為例:實際執(zhí)行時
- 等于
('a' extends 'a' | 'c' ? never : 'a') | ('b' extends 'a' | 'c' ? never: 'b')
尽纽; - 等于
(never) | ('b')
; - 等于
'b'
(任何元素和never
的聯(lián)合類型等于其本身)
聯(lián)合類型的條件判斷本質(zhì)上在進行“遍歷”童漩,這是個很有趣的語法特性蜓斧。我們這里暫不展開了,之后我會在實際的案例中解釋如何用這個特性解決一些需要依靠遍歷來破解的問題睁冬。
Extract<T, U>
: 從T
中提取可賦值給U
的類型
Exclude的反向操作就是Extract挎春,就是剔除不包含在U里的類型。這個太簡單了豆拨,一筆帶過:
type Extract<T, U> = T extends U ? T : never;
Pick<T, K>
: 從 T
中直奋,提取出所有鍵值在聯(lián)合類型 K
中的屬性
如下所示,我只想保留 Todo 類型里的 title 和 completed鍵值對:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
// type TodoPreview = {
// title: string,
// completed: boolean
// }
對Pick<T, K>
施禾,這里有兩個考點:
-
K
的取值:K
應該是T
里已經(jīng)存在的鍵值脚线,比如你傳個hello
需要拋錯 -
K
是個聯(lián)合類型,所以需要遍歷
我們看看實現(xiàn):
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
答案還是一個簡單的類型映射:
- 通過
K extends keyof T
限定 K 必須是T的所有鍵值的子集 - 用個
in
遍歷K
就行了 (in keyof
不是固定組合……)
Omit<T, K>
: 構(gòu)造一個除類型K
以外具有T
屬性的類型弥搞。
Omit是Pick的反向操作邮绿,排除對象 T
中的 K
鍵值。 Omit在名字上容易和Exclude搞混攀例。記住 Exclude 主要用在聯(lián)合類型船逮,而Omit主要用于對象類型上。如下所示粤铭,我要剔除Todo里的description和title兩個鍵值對:
type TodoPreview = Omit<Todo, 'description' | 'title'>;
// type TodoPreview = {
// completed: false,
// }
Omit<T, K>
對K沒有特別限制挖胃,只需要是正常的JS對象鍵類型(string | number | symbol
)就是了。實現(xiàn)上正好活用一下上面剛提到的方法類型——Pick
和Exclude
:
- 從T的所有鍵中剔除(
Exclude
)掉聯(lián)合類型K(Exclude<keyof T, K>
) - 提取(
Pick
)出所有鍵值在上一步得到的結(jié)果中的屬性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
小結(jié)
由于篇幅所限酱鸭,我們暫時先介紹8個最簡單吗垮,但又是最貼近實戰(zhàn)的工具方法。當你開始使用這些工具類型時凹髓,你的新手村小伙伴們一定會眼前一亮的烁登。之后的文章,我會進一步介紹剩下的內(nèi)置工具類型蔚舀,當然他們更加復雜也更能幫助我們提升認知饵沧。敬請期待。
文章同步發(fā)布于an-Onion 的 Github蝗敢。碼字不易捷泞,歡迎點贊。