刷完了type-challenges的所有簡單和中等難度的題目后,對TypeScript的類型操作有了一些新的理解和認識。特此用幾篇文章來記錄下一些重要的知識點。
本系列文章需要您對TypeScript有基本的了解
基本用法
JavaScript通過Object.keys()
獲取對象的所有屬性鍵值,而typescript主要關注的是類型操作,通過 keyof
操作符可以獲取對象中的所有鍵類型組成的聯合類型。
為了具體了解keyof
操作符的作用,我們通過一些例子來解釋下:
type Person = {
id: number;
name: string;
age: number;
};
type P1 = keyof Person; //'id' | 'name' | 'age'
keyof
操作符得到的是Person
類型的所有鍵值類型即'id'
,'name'
和'age'
三個字面量類型組成的聯合類型'id' | 'name' | 'age'
。
實際應用
接下來我會用一些例子講解keyof
的應用。
獲取對象所有屬性的類型
type P2 = Person[keyof Person]; // number | string
Person['key']
是查詢類型(Lookup Types), 可以獲取到對應屬性類型的類型;Person[keyof Person]
本質上是執行Person['id' | 'name' | 'age']
;- 由于聯合類型具有分布式的特性,
Person['id' | 'name' | 'age']
變成了Person['id'] | Person['name'] | Person['age']
;- 最后得到的結果就是
number | string
.
約束范型參數的范圍
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type P3 = MyPick<Person, 'id' | 'age'>
K extends keyof T
對K
進行了約束,只能是'id','name','age'
中的一個類型或者幾個類型組成的聯合類型;- 如果沒有這個約束,
{ [P in K]: T[P] }
則會報錯。
和映射類型組合實現某些功能
- 給對象類型的所有屬性加上
readonly
修飾符
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
type P4 = MyReadonly<Person>; // { readonly id: number; readonly name: string; readonly age: number; }
[P in keyof T]
是對所有屬性的鍵值類型進行遍歷,案例中得到的P
分別是'id'
,'name'
和'age'
;T[P]
是查詢類型,上面介紹過了,Person['id']
的結果是number
,Person['name']
的結果是string
,Person['age']
的結果是number
。- 將每個屬性類型添加
readonly
修飾符,最后的結果就是{ readonly id: number; readonly name: string; readonly age: number; }
- 去掉對象類型的某些屬性
微軟官是通過Pick
和exclude
組合來實現Omit
邏輯的,我們可以通過以下的代碼實現同樣的功能。
type MyOmit<T, K> = { [P in keyof T as P extends K ? never : P]: T[P] };
type P5 = MyOmit<Person, 'id' | 'name'> // {age: number;}
代碼中的
as P extends K ? never : P
這部分代碼叫做重映射 ,因為我們不一定需要的是P
,有些情況下需要對P
進行一些轉換;案例中K
中包含的P
鍵值類型則通過never
忽略了,相反則保留。所以最后的結果是{age: number;}
- 給對象類型添加新的屬性
type AppendToObject<T, U extends keyof any, V> = {[P in keyof T | U]: P extends keyof T ? T[P] : V}
type P6 = AppendToObject<Person, 'address', string> // { address: string; id: number; name: string; age: number; }
和條件類型組合實現功能
- 兩個對象類型合并成一個新的類型
type Merge<F extends Record<string, any>, S extends Record<string, any>> = {
[P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P] : never;
};
type Skill = {
run: () => void;
}
type P7 = Merge<Person, Skill>; // { id: number; name: string; age: number; run: () => void; }
案例中
P extends keyof S ? X : Y
的部分叫做條件類型
(后面也會單獨介紹)。代碼中的含義就是如果P
是F
的屬性類型,則取F[P]
,如果P
是S
的屬性類型,則取S[P]
。
小結
經過前面的介紹,應該對keyof
的使用有一些感覺了。下面我列一些代碼,大家可以感受下:
type _DeepPartial<T> = { [K in keyof T]?: _DeepPartial<T[K]> }
type Diff<T extends Record<string, any>, U extends Record<string, any>> = {
[P in keyof U | keyof T as P extends keyof U
? P extends keyof T
? never
: P
: P extends keyof T
? P
: never]: P extends keyof U ? U[P] : P extends keyof T ? T[P] : never;
};
這個實現邏輯涉及到了其他的知識點有點復雜,沒完全看懂沒關系,后面會介紹。