React 中的 TS 類型過濾原來是這么做的!
相信大家在閱讀同事寫的代碼或者優秀的開源庫的代碼時,一定見過各種各樣的風騷的TS寫法,不花點時間下去根本看不懂,換作是我們,可能就直接一個 any 完事了,但是真正當項目體積變大后,你會發現這些 TS騷操作真的很重要,因為它能很好地幫助你做靜態類型校驗
今天就來介紹一個在其它開源庫中見到的既花里胡哨,又實用的TS類型——TS類型過濾
自我介紹
TS類型過濾,英文名(我自己取的)叫 FilterConditionally,這是它完整的樣子👇
- type FilterConditionally<Source, Condition> = Pick<
- Source,
- {
- [K in keyof Source]: Source[K] extends Condition ? K : never
- }[keyof Source]
- >;
別看很復雜,其實非常有用,它可以從一個對象類型中過濾出你想要的,比如:
- interface Example {
- a: string; // ✅
- b: string; // ✅
- c: number; // ❌
- d: boolean; // ❌
- }
- type NewType = FilterConditionally<Sample, string>
- /*
- NewType 最終結果為:
- {
- a: string;
- b: string
- }
- */
相信大家已經這個類型的作用了,并且你們也很想讀懂它,沒關系,接下來由內而外、一步一步地介紹,一定讓你們完全讀懂,讀不懂評論區來噴我(我說著玩的~)
分步介紹
涉及的知識點比較多,怕有些不熟悉TS的同學懵逼,先來介紹其中幾個常見的基礎知識點
開胃小菜
不會耽誤大家多少時間的,會的小伙伴可以直接調過
keyof
關鍵詞 keyof 的名字叫 索引類型查詢操作符,它的作用就像它的字面意思一樣直白:xx的key值
- interface Example {
- a: string;
- b: string;
- c: number;
- d: boolean;
- }
- type Keys = keyof Example // 等價于 type Keys = 'a' | 'b' | 'c' | 'd'
你可以把 keyof 簡單理解為 JavaScript 中的 Object.keys
in
關鍵詞 in 可以遍歷枚舉類型,比如:
- type Keys = 'a' | 'b' | 'c' | 'd'
- type Obj = {
- [T in Keys]: string; // 遍歷Keys,把每個key都賦值string類型
- }
- /* 等價于
- type Obj = {
- a: string;
- b: string;
- c: string;
- d: string;
- }
- */
你可以把 in 簡單理解為 JavaScript 中 for...in 的 in 的作用
Conditional
第二個知識點是條件判斷,比如:
- interface A {}
- interface B extends A {} // B繼承于A
- // B是否繼承于A?若是,則為number類型;若不是,則為string類型
- type C = B extends A ? number : string // 等價于 type C = number
- // A是否繼承于B?若是,則為number類型;若不是,則為string類型
- type D = A extends B ? number : string // 等價于 type D = string
你可以把 A extends B ? number : string 簡單理解為 JavaScript 中的三元運算符
泛型
泛型我就不多做介紹了,不太了解的小伙伴可以直接看 TS文檔——泛型[1]
正餐開始
剛剛介紹完"開胃小菜",那就趁熱打鐵看一個簡單的類型
- type MarkUnwantedTypesAsNever<Source, Condition> ={
- [K in keyof Source]: Source[K] extends Condition ? K : never
- }
一句話介紹這個類型的作用就是:遍歷一個對象類型,將不想要的類型標記為 never
舉個例子🌰
- interface Example {
- a: string; // ✅
- b: string; // ✅
- c: number; // ❌
- d: boolean; // ❌
- }
- // 我只想要Example類型中的string類型的key,非string的就標記為never
- type MyType = MarkUnwantedTypesAsNever<Example, string>
- /*
- 等價于:
- type MyType = {
- a: 'a';
- b: 'b';
- c: never;
- d: never;
- }
- */
稍微講一下小細節,[K in keyof Example] 遍歷了 Example 這個對象類型,然后用條件判斷 Example[K] extends string ? K : never 給對應的 key 值賦值,假設遍歷第一個key值為 a,那么 Example[K] = Example[a] = string,此時就是 string extends string ? 'a' : never,string 肯定是繼承于 string 的,所以才會有這樣一個結果
此時大家心頭一驚,為什么要把類型搞成這樣??我們最后想要的結果不是要拿到一個 { a:string; b:string } 的類型嗎?別急,后面還有別的操作
再來看一個索引訪問接口屬性的小知識點
- type Value = {name: "zero2one"}["name"] // 等價于 type Value = "zero2one"
你可以把它簡單理解成 JavaScript 中訪問對象某個key對應的value
而在TS中還有另一種情況:
- type Value = {
- name: "zero2one";
- age: 23
- }["name" | "age"]
- // 等價于 type Value = "zero2one" | 23
而值為 never 的 key 值是無法被訪問到的:
- type Value = {
- name: "zero2one";
- age: never
- }["name" | "age"]
- // 等價于 type Value = "zero2one"
所以接下來可以看更復雜的類型了
- type MarkUnwantedTypesAsNever<Source, Condition> ={
- [K in keyof Source]: Source[K] extends Condition ? K : never
- }[keyof Source]
我們巧妙地利用 keyof 關鍵詞去遍歷訪問所有的接口屬性
- // 借用一下剛才例子的結果
- type MyType = {
- a: 'a';
- b: 'b';
- c: never;
- d: never;
- }['a' | 'b' | 'c' | 'd']
- /*
- 等價于:
- type MyType = 'a' | 'b'
- */
到此為止,我們所做的事情就是:把目標對象類型中想要類型的 key 值篩選了出來
別急別急,離成功就差一步之遙
最后登場的就是 Pick ,這個類型是TS內置的,簡單了解一下它的作用
- // Pick類型的實現
- type Pick<T, K extends keyof T> = {
- [P in K]: T[P];
- }
你可以不去詳細地讀懂它的實現,只需要知道 Pick 的作用就是:篩選出類型T 中指定的某些屬性
舉個簡單的例子:
- interface A {
- a: 1;
- b: 2;
- c: 3;
- d: 4;
- }
- type C = Pick<A, 'a' | 'c'> // 等價于 type C = { a: 1; c: 3 }
是的,就是這么簡單,好了可以來看最終的BOSS了
那么最后再從 Source 中篩選出對應屬性即可,回到本文具體的例子當中,圖中紅框中的值上文已得到為 type MyType = 'a' | 'b',那最后 Pick 一下就好了
- interface Example {
- a: string;
- b: string;
- c: number;
- d: boolean;
- }
- // 上文得到的結果
- type MyType = 'a' | 'b'
- type Result = Pick<Example, MyType> // 等價于 type Result = { a: string; b: string }
- // ---- 以上等價于 ---- //
- interface Example {
- a: string; // ✅
- b: string; // ✅
- c: number; // ❌
- d: boolean; // ❌
- }
- type NewType = FilterConditionally<Sample, string>
- /*
- NewType 最終結果為:
- {
- a: string;
- b: string
- }
- */
這就是文章開頭的結果獲取的全過程
實戰應用例子
正如本文標題所說的,TS類型過濾在很多優秀的開源庫中是非常常見的,比如我們熟悉的React中就是:
- type ElementType<PP = any> = {
- [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
- }[keyof JSX.IntrinsicElements] | ComponentType<P>;
最后
開源庫中像TS類型過濾這種場景太多太多了,希望今后大家遇到時能輕松讀懂。如果在屏幕前閱讀的你是后端,說不定也能在后端的開源框架源碼中看到它的身影呢~