ts中的extends

extends關(guān)鍵字在ts中在不同場(chǎng)景代表的含義不一樣:

  • 表示繼承
  • 表示約束
  • 表示分配(條件類型)

繼承

  class Animal {
    say() {
      console.log('say');
    }
  }
  class Dog extends Animal {}
  const dog = new Dog();
  dog.say();

泛型約束

type C = <T extends { name: string }>(arg: T) => any;

  function fn(cb: C) {
    cb({ name: '回調(diào)' });
  }
  fn((arg) => {
    console.log(arg.name);
  });

條件類型

 type Human = {
    name: string;
  };
  type Duck = {
    name: string;
  };
  type Bool = Duck extends Human ? 'yes' : 'no'; // yes

Bool的類型是 'yes'這是因?yàn)?Human 和 Duck 的類型完全相同,或者說 Human 類型的一切約束條件,Duck 都具備; 需要理解的是,這個(gè)A extends B 是指類型A可以分配給類型B, 而不是說類型A是類型B的子集.稍微擴(kuò)展下來詳細(xì)說明這個(gè)問題:

type Human = {
    name: string;
    desc: string;
  };
  type Duck = {
    name: string;
  };
  type Bool = Duck extends Human ? 'yes' : 'no'; // no

當(dāng)我們給Human加上一個(gè)desc屬性,發(fā)現(xiàn)此時(shí)Bool 是'no' 這是因?yàn)镈uck沒有類型為string的desc屬性,類型Duck不滿足類型Human的類型約束.因此 A extends B 是類型A可以分配給類型B 而不是說類型A是類型B的子集,理解extends在類型三元表達(dá)式里的用法非常重要。

例子
  type A1 = 'x' extends 'x' ? string : number; // string
  type A2 = 'x' | 'y' extends 'x' ? string : number; // number
  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'>; // ?

A1和A2是extends條件判斷的普通用法,和上面的判斷方法一樣。
P是帶參數(shù)T的泛型類型,其表達(dá)式和A1 A2的形式完全相同,A3是泛型類型P傳入?yún)?shù)'x' | 'y'得到的類型,如果將'x' | 'y'帶入泛型類的表達(dá)式,可以看到和A2類型的形式是完全一樣的,那是不是說明,A3和A2的類型就是完全一樣的呢?

  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'>; // string | number

是不是很反直覺?這個(gè)反直覺結(jié)果的原因就是所謂的分配條件類型

對(duì)與使用extends關(guān)鍵字的條件類型(即上面的三元表達(dá)式類型),如果extends前面的參數(shù)是一個(gè)泛型類型,當(dāng)傳入改參數(shù)的是聯(lián)合類型,則使用分配律計(jì)算最終的結(jié)果。分配律是指,將聯(lián)合類型的聯(lián)合項(xiàng)拆成單項(xiàng),分別代入條件類型,然后將每個(gè)單項(xiàng)代入得到的結(jié)果再聯(lián)合起來,得到最終的判斷結(jié)果。

  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'>; // string | number

該例中,extends的前參為T,T是一個(gè)泛型參數(shù)。在A3的定義中,給T傳入的是'x'和'y'的聯(lián)合類型'x' | 'y',滿足分配律,于是'x'和'y'被拆開,分別代入P<T>

P<'x' | 'y'> => P<'x'> | P<'y'>

'x'代入得到

'x' extends 'x' ? string : number // string

'y'代入得到

'y' extends 'x' ? string : number // number

然后將每一項(xiàng)代入得到的結(jié)果聯(lián)合起來,得到string | number

總之,滿足兩個(gè)要點(diǎn)即可適用分配律:第一,參數(shù)是泛型類型,第二,代入?yún)?shù)的是聯(lián)合類型

特殊的never

// never是所有類型的子類型
  type A1 = never extends 'x' ? string : number; // string
 
  type P<T> = T extends 'x' ? string : number;
  type A2 = P<never> // never

上面的示例中,A2和A1的結(jié)果竟然不一樣,看起來never并不是一個(gè)聯(lián)合類型,所以直接代入條件類型的定義即可,獲取的結(jié)果應(yīng)該和A1一直才對(duì)???
實(shí)際上,這里還是條件分配類型在起作用。never被認(rèn)為是空的聯(lián)合類型,也就是說,沒有聯(lián)合項(xiàng)的聯(lián)合類型,所以還是滿足上面的分配律,然而因?yàn)闆]有聯(lián)合項(xiàng)可以分配,所以P<T>的表達(dá)式其實(shí)根本就沒有執(zhí)行,所以A2的定義也就類似于永遠(yuǎn)沒有返回的函數(shù)一樣,是never類型的。

防止條件判斷中的分配

  type Invoke<T> = T extends 'x' ? string : number;
  type A = Invoke<'x' | 'y'>; // string | number;

  type Invoke2<T> = [T] extends 'x' ? string : number;
  type A2 = Invoke2<'x' | 'y'>; // number

在條件判斷類型的定義中,將泛型參數(shù)使用[ ]括號(hào)起來,即可阻斷條件判斷類型的分配,此時(shí),傳入?yún)?shù)T的類型將被當(dāng)做一個(gè)整體,不再分配。

高級(jí)類型中的應(yīng)用

  • Exclude
    Exclude是TS中的一個(gè)高級(jí)類型,其作用是從第一個(gè)聯(lián)合類型參數(shù)中,將第二個(gè)聯(lián)合類型中出現(xiàn)的聯(lián)合項(xiàng)全部排除,只留下沒有出現(xiàn)過的參數(shù).

實(shí)例

 type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // b | c

Exclude的定義是

type Exclude<T, U> = T extends U ? never : T

這個(gè)定義就利用了條件類型中的分配原則,來嘗試將實(shí)例拆開看看發(fā)生了什么:

  type Diff<T, U> = T extends U ? never : T;
  type R = Diff<'a' | 'b', 'a'>; // "b"

  // 等價(jià)于
  type R1 = Diff<'a', 'a'>; // never
  type R2 = Diff<'b', 'a'>; // 'b'
  type R = R1 | R2;
  • Extract

高級(jí)類型Extract和上面的Exclude剛好相反,它是將第二個(gè)參數(shù)的聯(lián)合項(xiàng)從第一個(gè)參數(shù)的聯(lián)合項(xiàng)中提取出來,當(dāng)然,第二個(gè)參數(shù)可以含有第一個(gè)參數(shù)沒有的項(xiàng)。
下面是其定義和一個(gè)例子

type Extract<T, U> = T extends U ? T : never
type A = Extract<'key1' | 'key2', 'key1'> // 'key1'
  • Pick
    extends的條件判斷,除了定義條件類型,還能在泛型表達(dá)式中用來約束泛型參數(shù)
// 高級(jí)類型Pick的定義
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}
 
interface A {
    name: string;
    age: number;
    sex: number;
}
 
type A1 = Pick<A, 'name'|'age'>
// 報(bào)錯(cuò):類型“"key" | "noSuchKey"”不滿足約束“keyof A”
type A2 = Pick<A, 'name'|'noSuchKey'>

Pick的意思是,從接口T中,將聯(lián)合類型K中涉及到的項(xiàng)挑選出來,形成一個(gè)新的接口,其中K extends keyof T則是用來約束K的條件,即,傳入K的參數(shù)必須使得這個(gè)條件為真,否則ts就會(huì)報(bào)錯(cuò),也就是說,K的聯(lián)合項(xiàng)必須來自接口T的屬性。
參考: http://www.qb5200.com/article/541259.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容