白話typescript中的【extends】和【infer】

你們好,我是小雨小雨,致力於分享有趣的、實用的技術文章。javascript

內容分爲翻譯和原創,若是有問題,歡迎隨時評論或私信,但願和你們一塊兒進步。java

分享不易,但願可以獲得你們的支持和關注。typescript

extends

typescript 2.8引入了條件類型關鍵字: extends,長這個樣子:數組

T extends U ? X : Y
複製代碼

看起來是否是有點像三元運算符: condition ? result(1) : result(2),用大白話能夠表示爲:markdown

若是T包含的類型 是 U包含的類型的 '子集',那麼取結果X,不然取結果Yapp

再舉幾個ts預約義條件類型的例子,加深理解:函數

type NonNullable<T> = T extends null | undefined ? never : T;

// 若是泛型參數 T 爲 null 或 undefined,那麼取 never,不然直接返回T。
let demo1: NonNullable<number>; // => number
let demo2: NonNullable<string>; // => string
let demo3: NonNullable<undefined | null>; // => never
複製代碼

分配式extends

T extends U ? X : Y
複製代碼

其實就是當上面的T爲聯合類型的時候,會進行拆分,有點相似數學中的分解因式:spa

(a + b) * c => ac + bc翻譯

再舉個官網的例子:code

type Diff<T, U> = T extends U ? never : T; // 找出T的差集
type Filter<T, U> = 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, string>; // => number
複製代碼

咱們再來看看infer。

infer

在extends語句中,還支持infer關鍵字,能夠推斷一個類型變量,高效的對類型進行模式匹配。可是,這個類型變量只能在true的分支中使用。

// 內置 ReturnType
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
複製代碼

不知道初學ts的朋友們看完這個介紹是否是一臉懵逼,反正以前我是...

其實理解以後很簡單,這裏直接說下個人理解,應該還算簡單易懂:

infer X 就至關於聲明瞭一個變量,這個變量隨後可使用,是否是有點像for循環裏面的聲明語句?

for (let i = 0, len = arr.length; i < len; i++) {
    // do something
}
複製代碼

不一樣的是,infer X的這個位置本應該有一個寫死的類型變量,只不過用infer R替換了,更靈活。

須要注意的是infer聲明的這個變量只能在true分支中使用

仍是舉幾個例子,加深理解,紙上談兵終覺淺嘛:

例子一

// 解讀: 若是泛型變量T是 () => infer R的`子集`,那麼返回 經過infer獲取到的函數返回值,不然返回boolean類型
type Func<T> = T extends () => infer R ? R : boolean;

let func1: Func<number>; // => boolean
let func2: Func<''>; // => boolean
let func3: Func<() => Promise<number>>; // => Promise<number>
複製代碼

例子二

// 同上,但當a、b爲不一樣類型的時候,返回不一樣類型的聯合類型
type Obj<T> = T extends {a: infer VType, b: infer VType} ? VType : number;

let obj1: Obj<string>; // => number
let obj2: Obj<true>; // => number
let obj3: Obj<{a: number, b: number}>; // => number
let obj4: Obj<{a: number, b: () => void}>; // => number | () => void
複製代碼

例子三(Vue3中的UnwrapRef)

// 若是泛型變量T是ComputedRef的'子集',那麼使用UnwrapRefSimple處理infer指代的ComputedRef泛型參數V
// 不然進一步判斷是否爲Ref的'子集',進一步UnwrapRefSimple
export type UnwrapRef<T> = T extends ComputedRef<infer V>
  ? UnwrapRefSimple<V>
  : T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>
    
// 我是分割線
    
// 若是T爲Function | CollectionTypes | BaseTypes | Ref之一的'子集',直接返回。
// 不然判斷是否爲數組的'子集',不是的話視爲object,調用UnwrappedObject
type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
  ? T
  : T extends Array<any> ? T : T extends object ? UnwrappedObject<T> : T

// 我是分割線
// 調用UnwrapRef,產生遞歸效果,解決了ts類型遞歸
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

// 我是分割線
    
// 泛型Ref
export interface Ref<T = any> {
  [Symbol()]: true
  value: T
}

// 我是分割線

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  readonly value: T
}

// 我是分割線

export interface WritableComputedRef<T> extends Ref<T> {
  readonly effect: ReactiveEffect<T>
}
複製代碼

建議本身捋一遍。

總結

ts提供的extends和infer大大增長了類型判斷的靈活性和複用性,雖然用與不用均可以,但能熟練地使用高級特性將大大提高ts推斷的效率和代碼類型的可讀性。

若有問題,歡迎指出。

勞動節快樂!

相關文章
相關標籤/搜索