typescript type 分配條件型別

goblin_pitcher發表於2023-05-18

接上文type challenge(easy部分)
關於分配條件型別,官方文件描述地址

之前看的時候沒真正理解,關於聯合型別的分配條件,官方文件其實也沒有講得很明白,和翻譯無關,英文文件一樣很模糊,這些天做type challenge,發現有些題做出來的結果和預期不太一致,所以重新梳理這塊內容。

先說結論

聯合型別什麼時候會分配,必須符合4個條件(後面直接用條件1、條件2等代指下麵條件):

  1. 首先,只分配extends前的內容

    • 無論這個extends是不是子斷言語句中的
    • 例如type Test<T> = 'b' extends 'b' ? (T extends 'b' ? true: false) : false;, 其中的T extends 'b'在子語句中,但事實上依舊是有效的
  2. 分配的內容未做任何處理

    • type Test<T> = keyof T extends null ? never: false;Tkeyof運算子處理了,因此不會分配
    • 官方文件中,提到避免分配的方法type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;,能規避分配也是這個道理
  3. 分配內容必須作為引數傳入
  4. 傳入時是聯合型別

相關題目與解析

驗證條件1

type Test<T> = 'b' extends 'b' ? (T extends 'b' ? true: false) : false;
Test<'a'| 'b'> // boolean

可見在子條件中的extends也符合自動分配,否則'a'|'b' extends 'b'會返回false,而不是true|false

驗證條件2

發現這個問題是在DeepReadonly,題目地址

這一題一看看過去,直接寫出如下:

type DeepReadonly<T> = keyof T extends never ? T : {readonly [k in keyof T]: DeepReadonly<T[k]>};

但是發現對於測試用例X2不生效

type X2 = { a: string } | { b: number };
DeepReadonly<X2> // { a: string } | { b: number }

仔細看,雖然X2是聯合型別,但keyof T extends never顯然不符合前面說的條件2,因此不會自動分配,而keyof ({ a: string } | { b: number })值為never。因此該題正確寫法如下:

type DeepReadonly<T> = {
  readonly [P in keyof T]: keyof T[P] extends never ? T[P] : DeepReadonly<T[P]>;
};

驗證條件3

顯然,普通使用extands不會觸發自動分配

type Test = 'a'|'b' extends 'a' ? true: false; // false

那麼,假設傳入的引數是聯合型別,extends前的物件也是聯合型別呢?

type Test<T> = 'b' extends 'b' ? (keyof T extends 'b' ? true: false) : false;
type Result = Test<{a:1,b:string}|{a:2,b:number}> // false

這裡,引數T是聯合型別,但extends前進行了keyof處理,但keyof {a:1,b:string}|{a:2,b:number}結果為'a'|'b',依然是聯合型別,若這裡進行了自動分配,結果應是boolean而非false

根據結果來看,這裡並未進行分配,這個例子同時違背了條件2條件3

驗證條件4

type Test<T> = 'a'|'b' extends 'b' ? T: false;
Test<5> // false

條件4顯而易見,官方文件上已經說的很明確了。

不注意優先順序導致的錯誤

在測試分配條件型別的規律時,曾因為一條用例卡了半天,用例如下:

type A = keyof null|undefined; // undefined
type UndefinedExtendsNull = undefined extends null ? true: false; //false
type Test<T> = keyof T extends null ? true: false;
Test<null|undefined>; // true !!!!

此時已經知道了,keyof T會避免自動分配,因此對於Test<null|undefined>,可以寫成

keyof null|undefined extends null ? true : false; // 這裡有個坑...

keyof null|undefined結果是undefined,但是

type UndefinedExtendsNull = undefined extends null ? true: false; //false

結果是false,同樣的式子,結果不一樣,一開始我以為是分配規律的理解有問題,但即使分配了,結果也應該是true|false,也就是boolean,而不是true

後來發現,type是有優先順序的,且keyof優先順序高於|.

按理說keyof null|undefined結果應該是never,之所以會顯示結果是undefined,是因為優先順序運算:

keyof null|undefined -> (keyof null)|undefined -> never|undefined -> undefined

在實際寫型別的時候,要重點注意優先順序問題

相關文章