大家好,我是小雨小雨愕贡,致力于分享有趣的草雕、實用的技術(shù)文章。
內(nèi)容分為翻譯和原創(chuàng)固以,如果有問題墩虹,歡迎隨時評論或私信,希望和大家一起進(jìn)步憨琳。
分享不易诫钓,希望能夠得到大家的支持和關(guān)注。
extends
typescript 2.8引入了條件類型關(guān)鍵字: extends栽渴,長這個樣子:
T extends U ? X : Y
看起來是不是有點像三元運算符: condition ? result(1) : result(2)
尖坤,用大白話可以表示為:
如果
T
包含的類型 是U
包含的類型的 '子集',那么取結(jié)果X
闲擦,否則取結(jié)果Y
慢味。
再舉幾個ts預(yù)定義條件類型的例子,加深理解:
type NonNullable<T> = T extends null | undefined ? never : T;
// 如果泛型參數(shù) 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
其實就是當(dāng)上面的T為聯(lián)合類型的時候寞忿,會進(jìn)行拆分驰唬,有點類似數(shù)學(xué)中的分解因式:
(a + b) * c => ac + bc
再舉個官網(wǎng)的例子:
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">
// 相當(dāng)于
// <'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
關(guān)鍵字叫编,可以推斷一個類型變量辖佣,高效的對類型進(jìn)行模式匹配。但是搓逾,這個類型變量只能在true的分支中使用卷谈。
// 內(nèi)置 ReturnType
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
不知道初學(xué)ts的朋友們看完這個介紹是不是一臉懵逼,反正之前我是...
其實理解之后很簡單霞篡,這里直接說下我的理解世蔗,應(yīng)該還算簡單易懂:
infer X 就相當(dāng)于聲明了一個變量,這個變量隨后可以使用朗兵,是不是有點像for循環(huán)里面的聲明語句污淋?
for (let i = 0, len = arr.length; i < len; i++) {
// do something
}
不同的是,infer X的這個位置本應(yīng)該有一個寫死的類型變量余掖,只不過用infer R替換了寸爆,更靈活。
需要注意的是infer聲明的這個變量只能在true分支中使用
還是舉幾個例子浊吏,加深理解而昨,紙上談兵終覺淺嘛:
例子一
// 解讀: 如果泛型變量T是 () => infer R的`子集`,那么返回 通過infer獲取到的函數(shù)返回值找田,否則返回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>
例子二
// 同上歌憨,但當(dāng)a、b為不同類型的時候墩衙,返回不同類型的聯(lián)合類型
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泛型參數(shù)V
// 否則進(jìn)一步判斷是否為Ref的'子集',進(jìn)一步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之一的'子集'漆改,直接返回心铃。
// 否則判斷是否為數(shù)組的'子集',不是的話視為object挫剑,調(diào)用UnwrappedObject
type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
? T
: T extends Array<any> ? T : T extends object ? UnwrappedObject<T> : T
// 我是分割線
// 調(diào)用UnwrapRef去扣,產(chǎn)生遞歸效果,解決了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>
}
建議自己捋一遍樊破。
總結(jié)
ts提供的extends和infer大大增加了類型判斷的靈活性和復(fù)用性愉棱,雖然用與不用都可以,但能熟練地使用高級特性將大大提升ts推斷的效率和代碼類型的可讀性哲戚。
如有問題奔滑,歡迎指出。
勞動節(jié)快樂顺少!