1. 程式人生 > >白話typescript中的【extends】和【infer】(含vue3的UnwrapRef)

白話typescript中的【extends】和【infer】(含vue3的UnwrapRef)

> 大家好,我是小雨小雨,致力於分享有趣的、實用的技術文章。 > 內容分為翻譯和原創,如果有問題,歡迎隨時評論或私信,希望和大家一起進步。 > 分享不易,希望能夠得到大家的支援和關注。 ## extends typescript 2.8引入了條件型別關鍵字: extends,長這個樣子: ```typescript T extends U ? X : Y ``` 看起來是不是有點像三元運算子: `condition ? result(1) : result(2)`,用大白話可以表示為: > 如果`T`包含的型別 是 `U`包含的型別的 '子集',那麼取結果`X`,否則取結果`Y`。 再舉幾個ts預定義條件型別的例子,加深理解: ```typescript type NonNullable = T extends null | undefined ? never : T; // 如果泛型引數 T 為 null 或 undefined,那麼取 never,否則直接返回T。 let demo1: NonNullable; // => number let demo2: NonNullable; // => string let demo3: NonNullable; // => never ``` ### 分配式extends ```typescript T extends U ? X : Y ``` 其實就是當上面的T為聯合型別的時候,會進行拆分,有點類似數學中的分解因式: > (a + b) * c => ac + bc 再舉個官網的例子: ```typescript type Diff = T extends U ? never : T; // 找出T的差集 type Filter = T extends U ? T : never; // 找出交集 type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "b" | "d" // <"a" | "b" | "c" | "d", "a" | "c" | "f"> // 相當於 // <'a', "a" | "c" | "f"> | // <'b', "a" | "c" | "f"> | // <'c', "a" | "c" | "f"> | // <'d', "a" | "c" | "f"> type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "a" | "c" // <"a" | "b" | "c" | "d", "a" | "c" | "f"> 同上 let demo1: Diff; // => number ``` 我們再來看看infer。 ## infer 在extends語句中,還支援`infer`關鍵字,可以推斷一個型別變數,高效的對型別進行模式匹配。但是,這個型別變數只能在true的分支中使用。 ```typescript // 內建 ReturnType type ReturnType = T extends (...args: any[]) => infer R ? R : any; ``` 不知道初學ts的朋友們看完這個介紹是不是一臉懵逼,反正之前我是... 其實理解之後很簡單,這裡直接說下我的理解,應該還算簡單易懂: > infer X 就相當於聲明瞭一個變數,這個變數隨後可以使用,是不是有點像for迴圈裡面的宣告語句? ```javascript for (let i = 0, len = arr.length; i < len; i++) { // do something } ``` >
不同的是,infer X的這個位置本應該有一個寫死的型別變數,只不過用infer R替換了,更靈活。 > 需要注意的是infer宣告的這個變數只能在true分支中使用 還是舉幾個例子,加深理解,紙上談兵終覺淺嘛: ### 例子一 ```typescript // 解讀: 如果泛型變數T是 () => infer R的`子集`,那麼返回 通過infer獲取到的函式返回值,否則返回boolean型別 type Func = T extends () => infer R ? R : boolean; let func1: Func; // => boolean let func2: Func<''>; // => boolean let func3: Func<() => Promise>; // => Promise ``` ### 例子二 ```typescript // 同上,但當a、b為不同型別的時候,返回不同型別的聯合型別 type Obj = T extends {a: infer VType, b: infer VType} ? VType : number; let obj1: Obj; // => number let obj2: Obj; // => number let obj3: Obj<{a: number, b: number}>; // => number let obj4: Obj<{a: number, b: () => void}>; // => number | () => void ``` ### 例子三(Vue3中的UnwrapRef) ```typescript // 如果泛型變數T是ComputedRef的'子集',那麼使用UnwrapRefSimple處理infer指代的ComputedRef泛型引數V // 否則進一步判斷是否為Ref的'子集',進一步UnwrapRefSimple export type UnwrapRef = T extends ComputedRef ? UnwrapRefSimple : T extends Ref ? UnwrapRefSimple : UnwrapRefSimple // 我是分割線 // 如果T為Function | CollectionTypes | BaseTypes | Ref之一的'子集',直接返回。 // 否則判斷是否為陣列的'子集',不是的話視為object,呼叫UnwrappedObject type UnwrapRefSimple = T extends Function | CollectionTypes | BaseTypes | Ref ? T : T extends Array ? T : T extends object ? UnwrappedObject : T // 我是分割線 // 呼叫UnwrapRef,產生遞迴效果,解決了ts型別遞迴 type UnwrappedObject = { [P in keyof T]: UnwrapRef } & SymbolExtract // 我是分割線 // 泛型Ref export interface Ref { [Symbol()]: true value: T } // 我是分割線 export interface ComputedRef extends WritableComputedRef { readonly value: T } // 我是分割線 export interface WritableComputedRef extends Ref { readonly effect: ReactiveEffect } ``` 建議自己捋一遍。 ## 總結 ts提供的extends和infer大大增加了型別判斷的靈活性和複用性,雖然用與不用都可以,但能熟練地使用高階特性將大大提升ts推斷的效率和程式碼型別的可讀性。 如有問題,歡迎指出。 勞動節快樂! ![](https://user-gold-cdn.xitu.io/2020/5/3/171da3c0a55c24c7?w=240&h=240&f=jpeg&