最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文詳解typeScript的extends關(guān)鍵字

 更新時(shí)間:2023年03月28日 14:50:56   作者:鯊叔  
這篇文章主要為大家介紹了typeScript的extends關(guān)鍵字使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

聲明: 以下文章所包含的結(jié)論都是基于 typeScript@4.9.4 版本所取得的。

extends 是 typeScript 中的關(guān)鍵字。在 typeScript 的類(lèi)型編程世界里面,它所扮演的角色實(shí)在是太重要了,所以,我們不得不需要重視它,深入學(xué)習(xí)它。在我看來(lái),掌握它就是進(jìn)入高級(jí) typeScript 類(lèi)型編程世界的敲門(mén)磚。但是,現(xiàn)實(shí)是,它在不同的上下文中,具體不同的,相差很大的語(yǔ)義。如果沒(méi)有深入地對(duì)此進(jìn)行梳理,它會(huì)給開(kāi)發(fā)者帶來(lái)很大的困惑。梳理并深入學(xué)習(xí)它,最后掌握它,這就是我編寫(xiě)這篇文章的初衷。

extends 的幾個(gè)語(yǔ)義

讓我們開(kāi)門(mén)見(jiàn)山地說(shuō)吧,在 typeScript 在不同的上下文中,extends 有以下幾個(gè)語(yǔ)義。不同語(yǔ)義即有不同的用途:

  • 用于表達(dá)類(lèi)型組合;
  • 用于表達(dá)面向?qū)ο笾小割?lèi)」的繼承
  • 用于表達(dá)泛型的類(lèi)型約束;
  • 在條件類(lèi)型(conditional type)中,充當(dāng)類(lèi)型表達(dá)式,用于求值。

extends 與 類(lèi)型組合/類(lèi)繼承

extends 可以跟 interface 結(jié)合起來(lái)使用,用于表達(dá)類(lèi)型組合。

示例 1-1

interface ChildComponentProps {
    onChange: (val: string)=> void
}
interface ParentComponentProps extends ChildComponentProps {
    value: string
}

在 react 組件化開(kāi)發(fā)模式中,存在一種自底向上的構(gòu)建模式 - 我們往往會(huì)先把所有最底層的子組件的 props 構(gòu)建好,最后才定義 container component(負(fù)責(zé)提升公共 state,聚合和分發(fā) props) 的 props。此時(shí),inferface 的 extends 正好能表達(dá)這種語(yǔ)義需求 - 類(lèi)型的組合(將所有子組件的 props 聚合到一塊)。

當(dāng)然,interfaceextends 從句是可以跟著多個(gè)組合對(duì)象,多個(gè)組合對(duì)象之間用逗號(hào),隔開(kāi)。比如 ParentComponentProps組合多個(gè)子組件的 props

示例 1-2

interface ChildComponentProps {
    onChange: (val: string)=> void
}
interface ChildComponentProps2 {
    onReset: (value: string)=> void
}
interface ParentComponentProps extends ChildComponentProps, ChildComponentProps2 {
    value: string
}

注意,上面指出的是「多個(gè)組合對(duì)象」,這里也包括了Class。對(duì),就是普通面向概念中的「類(lèi)」。也就是說(shuō),下面的代碼也是合法的:

示例 1-3

interface ChildComponentProps {
    onChange: (val: string)=> void
}
interface ChildComponentProps2 {
    onReset: (value: string)=> void
}
class SomeClass {
    private name!: string // 變量聲明時(shí),變量名跟著一個(gè)感嘆號(hào)`!`,這是「賦值斷言」的語(yǔ)法
    updateName(name:string){
        this.name = name || ''
    }
}
interface ParentComponentProps extends
ChildComponentProps,
ChildComponentProps2,
SomeClass {
    value: string
}

之所以這也是合法的,一切源于一個(gè)特性:在 typeScript 中,一個(gè) class 變量既是「值」也是「類(lèi)型」。在interface extends class的上下文中,顯然是取 class 是「類(lèi)型」的語(yǔ)義。一個(gè) interface extends 另外一個(gè) class,可以理解為 interface 拋棄這個(gè) class 的所有實(shí)現(xiàn)代碼,只是跟這個(gè) class 的「類(lèi)型 shape」 進(jìn)行組合。還是上面的示例代碼中,從類(lèi)型 shape 的角度,SomeClass 就等同于下面的 interface:

示例 1-4

interface SomeClass {
   name: string
   updateName: (name:string)=> void
}

好了,以上就是 extends 關(guān)鍵字的「類(lèi)型組合」的語(yǔ)義。事情開(kāi)始發(fā)生了轉(zhuǎn)折。

如果某個(gè) interface A 繼承了某個(gè) class B,那么這個(gè) interface A 還是能夠被其他 interface 去繼承(或者說(shuō)組合)。但是,如果某個(gè) class 想要 implements 這個(gè) interface A,那么這個(gè) class 只能是 class B 本身或者 class B 的子類(lèi)。

示例 1-5

class Control {
   private state: any;
  constructor(intialValue: number){
    if(intialValue > 10){
      this.state = false
    }else {
      this.state = true
    }
  }
  checkState(){
    return this.state;
  }
}
interface SelectableControl extends Control {
  select(): void;
}
// 下面的代碼會(huì)報(bào)錯(cuò):Class 'DropDownControl' incorrectly implements interface
// 'SelectableControl'.
// Types have separate declarations of a private property 'state'.(2420)
class DropDownControl  implements SelectableControl {
  private state = false;
  checkState(){
    // do something
  }
  select(){
    // do something
  }
}

要想解決這個(gè)問(wèn)題,class DropDownControl必須要繼承 Control class 或者Control class 的子類(lèi):

示例 1-6

class Control {
   private state: any;
  constructor(intialValue: number){
    if(intialValue > 10){
      this.state = false
    }else {
      this.state = true
    }
  }
  checkState(){
    return this.state;
  }
}
interface SelectableControl extends Control {
  select(): void;
}
// 下面的代碼就不會(huì)報(bào)錯(cuò),且能得到預(yù)期的運(yùn)行結(jié)果
class DropDownControl  extends Control  implements SelectableControl {
  // private state = false;
  //checkState(){
    // do something
  //}
  select(){
    // do something
  }
}
const dropDown = new DropDownControl(1);
dropDown.checkState(); // Ok
dropDown.select(); // Ok

上面這個(gè)示例代碼扯出了 extends 關(guān)鍵字的另外一個(gè)語(yǔ)義 - 「繼承」。當(dāng)extends用于 typeScript 的類(lèi)之間,它的準(zhǔn)確語(yǔ)義也就是 ES6 中面向?qū)ο笾小竐xtends」關(guān)鍵字的語(yǔ)義。AClass extends BClass 不再應(yīng)該解讀為「類(lèi)型的組合」而是面向?qū)ο缶幊讨械摹窤Class 繼承 BClass」和「AClass 是父類(lèi) BClass 的子類(lèi)」。與此同時(shí),值得指出的是,此時(shí)的 extends 關(guān)鍵字是活在了「值的世界」, 遵循著 ES6 中 extends關(guān)鍵字一樣的語(yǔ)義。比較顯著的一點(diǎn)就是,ts 中的 extends 也是不能在同一時(shí)間去繼承多個(gè)父類(lèi)的。比如,下面的代碼就會(huì)報(bào)錯(cuò):

示例 1-7

class A {}
class B {}
// 報(bào)錯(cuò): Classes can only extend a single class.(1174)
class C extends A,B {
}

關(guān)于具有「繼承」語(yǔ)義的 extends 更多行為特性的闡述已經(jīng)屬于面向?qū)ο缶幊谭妒降姆懂犃?,這里就不深入討論了,有興趣的同學(xué)可以自行去了解。

至此,我們算是了解 extends 關(guān)鍵字跟 interfaceclass 結(jié)合起來(lái)所表達(dá)的兩種不同的語(yǔ)義:

  • 類(lèi)型的組合
  • 面向?qū)ο蟾拍钪小割?lèi)的繼承」

接下來(lái),我們看看用于表達(dá)泛型類(lèi)型約束的 extends

extends 與類(lèi)型約束

更準(zhǔn)確地說(shuō),這一節(jié)是要討論 extends 跟泛型形參結(jié)合時(shí)候的「類(lèi)型約束」語(yǔ)義。在更進(jìn)一步討論之前,我們不妨先復(fù)習(xí)一下,泛型形參聲明的語(yǔ)法以及我們可以在哪些地方可以聲明泛型形參。

具體的泛型形參聲明語(yǔ)法是:

  • 標(biāo)識(shí)符后面用尖括號(hào)<>包住一個(gè)或者多個(gè)泛型形參
  • 多個(gè)泛型形參用,號(hào)隔開(kāi)
  • 泛型新參的名字可以隨意命名(我們見(jiàn)得最多就是使用單個(gè)英文字母T,U之類(lèi)的)。

在 typeScript 中,我們可以在以下地方去聲明一個(gè)泛型形參。

在普通的函數(shù)聲明中:

function dispatch<A>(action: A): A {
    // Do something
}

在函數(shù)表達(dá)式形態(tài)的類(lèi)型注解中:

const dispatch: <A>(action: A)=> A =  (action)=> {
  return action
}
// 或者
interface Store {
 dispatch: <A>(action: A)=> A
}

interface 的聲明中:

interface Store<S> {
 dispatch: <A>(action: A)=> A
 reducer: <A>(state: S,action: A)=> S
}

class 的聲明中:

class GenericAdd<AddableType> {
  zeroValue!: AddableType;
  add!: (x: AddableType, y: AddableType) => AddableType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
    return x + y;
};

在自定義類(lèi)型聲明中:

 type Dispatch<A>=(action:A)=> A
  • 在類(lèi)型推導(dǎo)中: typeScript // 此處,F(xiàn) 和 Rest 就是泛型形參 type GetFirstLetter<S> = S extends `${infer F extends `${number}`}${infer Rest}` ? F : S;  以上就是簡(jiǎn)單梳理后的可以產(chǎn)生泛型形參的地方,可能還有疏漏,但是這里就不深入發(fā)掘了。

下面重點(diǎn)來(lái)了 - 凡是有泛型形參的地方,我們都可以通過(guò) extends 來(lái)表達(dá)類(lèi)型約束。這里的類(lèi)型約束展開(kāi)說(shuō)就是,泛型形參在實(shí)例化時(shí)傳進(jìn)來(lái)的類(lèi)型實(shí)參必須要滿足我們所聲明的類(lèi)型約束。到這里,問(wèn)題就來(lái)了,我們?cè)撛鯓觼?lái)理解這里的「滿足」呢?在深究此問(wèn)題之前,我們來(lái)看看類(lèi)型約束的語(yǔ)法:

`泛型形參` extends `某個(gè)類(lèi)型`

為了引出上面所說(shuō)「滿足」的理解難題,我們不妨先看看下面的示例的代碼:

示例 2-1

// case 1
type UselessType<T extends number> = T;
type Test1 = UselessType<any> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test1_1 = UselessType<number|string> // 這里會(huì)報(bào)錯(cuò)嗎?
// case 2
type UselessType2<T extends {a:1, b:2}> = T;
type Test2 = UselessType2<{a:1, b:2, c:3}> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test2_1 = UselessType2<{a:1}> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test2_2 = UselessType2<{[key:string]: any}> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test2_3 = {a:1, b:2} extends  {[key:string]: any} ? true : false
// case 3
class BaseClass {
    name!: string
}
class SubClass extends  BaseClass{
    sayHello!: (name: string)=> void
}
class SubClass2 extends  SubClass{
    logName!: ()=> void
}
type UselessType3<T extends SubClass> = T;
type Test3 = UselessType3<{name: '鯊叔'}> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test3_1 = UselessType3<SubClass> // 這里會(huì)報(bào)錯(cuò)嗎?
type Test3_2 = UselessType3<BaseClass> // 這里會(huì)報(bào)錯(cuò)嗎?

不知道讀者朋友們?cè)跊](méi)有把上述代碼拷貝到 typeScript 的 playground 里面去驗(yàn)證之前你是否能全部猜中。如果能,證明你對(duì) extends 在類(lèi)型約束的語(yǔ)義上下文中的行為表現(xiàn)已經(jīng)掌握的很清楚了。如果不能,請(qǐng)?jiān)试S我為你娓娓道來(lái)。

相信有部分讀者了解過(guò) typeScript 的類(lèi)型系統(tǒng)的設(shè)計(jì)策略。由于 js 是一門(mén)動(dòng)態(tài)弱類(lèi)型的腳本語(yǔ)言,再加上需要考慮 typeScript 與 js 的互操性和兼容性。所以, typeScript 類(lèi)型系統(tǒng)被設(shè)計(jì)為一個(gè)「structural typing」系統(tǒng)(結(jié)構(gòu)化類(lèi)型系統(tǒng))。所謂的結(jié)構(gòu)化類(lèi)型系統(tǒng)的一個(gè)顯著的特點(diǎn)就是 - 具有某個(gè)類(lèi)型 A 的值是否能夠賦值給另外一個(gè)類(lèi)型 B 的值的依據(jù)是,類(lèi)型 A 的類(lèi)型結(jié)構(gòu)是否跟類(lèi)型 B 的類(lèi)型結(jié)構(gòu)是否兼容。 而類(lèi)型之間是否兼容看重的類(lèi)型的結(jié)構(gòu)而不是類(lèi)型的名字。再說(shuō)白一點(diǎn),就是 B 類(lèi)型有的屬性和方法,你 A 類(lèi)型也必須有。到這里,就很容易引出一個(gè)廣為大眾接受的,用于理解類(lèi)型「可賦值性」行為的心智模型,即:

  • 用集合的角度去看類(lèi)型。故而這里有「父集」和 「子集」的概念,「父集」包含 「子集」;
  • 在 typeScript 的類(lèi)型系統(tǒng)中, 子集類(lèi)型是可以賦值給父集類(lèi)型。
  • 在泛型形參實(shí)例化時(shí),如果 extends 前面的類(lèi)型是它后面的類(lèi)型的子集,那么我們就說(shuō)當(dāng)前的實(shí)例化是「滿足」我們所聲明的類(lèi)型約束的。

以下是 示例 2-1 的運(yùn)行結(jié)果:

實(shí)際上,上面的那個(gè)心智模型是無(wú)法匹配到以上示例在 typeScript@4.9.4 上的運(yùn)行結(jié)果。以上面這個(gè)心智模型(子集類(lèi)型能賦值給父集類(lèi)型,反之則不然)來(lái)看示例的運(yùn)行結(jié)果,我們會(huì)有下面的直覺(jué)認(rèn)知偏差:

  • case 1 中,anynumber 的父集,為什么它能賦值給 number 類(lèi)型的值?
  • case 1 中,number | string 應(yīng)該是 number 的父集,所以,它不能賦值給 number 類(lèi)型的值。
  • case 1 中,number & string 應(yīng)該是 number 的父集,按理說(shuō),這里應(yīng)該報(bào)錯(cuò),但是為什么卻沒(méi)有?
  • case 2 中,{a:1}{a:1,b:2} 的子集,按理說(shuō),它能賦值給 {a:1,b:2}類(lèi)型的值啊,為什么會(huì)報(bào)錯(cuò)?
  • case 3 中,感覺(jué){name: '鯊叔'}SubClass 的子集,按理說(shuō),它能賦值給 SubClass類(lèi)型的值啊,為什么會(huì)報(bào)錯(cuò)?
  • case 3 中,感覺(jué)BaseClassSubClass 的子集,按理說(shuō),它能賦值給 SubClass類(lèi)型的值啊,為什么會(huì)報(bào)錯(cuò)?

經(jīng)過(guò)反復(fù)驗(yàn)證和查閱資料,正確的認(rèn)知如下:

  • case 1 中,any 是任何類(lèi)型的子集,也是任何類(lèi)型的父集。這里 typeScript 往寬松方向去處理,即取 number 的子集之意;
  • number | string 之所以不能賦值給 number ,并不是因?yàn)?number | stringnumber 的父集,而是因?yàn)槁?lián)合類(lèi)型遇到 extends關(guān)鍵字所產(chǎn)生的「分配律」的結(jié)果。即是因?yàn)?number|string extends number的結(jié)果等于 (number extend number) | (string extends number)的結(jié)果。顯然,(number string extends number的值是 false 的,所以,整個(gè)類(lèi)型約束就不滿足;
  • 對(duì)象類(lèi)型的類(lèi)型不能采用 子集類(lèi)型 extends 父集類(lèi)型 = true的心智模型來(lái)理解。而是得采用 父集類(lèi)型 extends 子集類(lèi)型 = true。與此同時(shí),當(dāng)子集類(lèi)型中有明確字面量 key-value 對(duì)的時(shí)候,父集類(lèi)型中也必須需要有。否則的話,就是不可賦值給子集類(lèi)型。
  • number & string 應(yīng)該被視為對(duì)象類(lèi)型的類(lèi)型,遵循上面一條的規(guī)則。

基于上面的正確認(rèn)知,我們不妨把我們的心智模型修正一下:

  • 應(yīng)該使用「父類(lèi)型」和「子類(lèi)型」的概念去理解滿足類(lèi)型約束背后所遵循的規(guī)則;
  • 在類(lèi)型約束 AType extends BType 中,如果 ATypeBType的子類(lèi)型,那么我們就會(huì)說(shuō) AType 是滿足我們所聲明的類(lèi)型約束的;
  • 根據(jù)下面的 「ts 類(lèi)型層級(jí)關(guān)系圖」來(lái)判斷兩種類(lèi)型的父-子類(lèi)型關(guān)系:

注:

1)A -> B表示「A 是 B 的父類(lèi)型,B 是 A 的子類(lèi)型」;

2)strictNullChecks 編譯標(biāo)志位打開(kāi)后,undefined,voidnull就不會(huì)成為 typeScript 類(lèi)型系統(tǒng)的一層,因?yàn)樗鼈兪遣荒苜x值給其他類(lèi)型的。

關(guān)于上面這張圖,有幾點(diǎn)可以單獨(dú)拿出來(lái)強(qiáng)調(diào)一下:

  • any 無(wú)處不在。它既是任何類(lèi)型的子類(lèi)型,也是任何類(lèi)型的父類(lèi)型,甚至可能是任意類(lèi)型自己。所以,它可以賦值給任何類(lèi)型;
  • {} 充當(dāng) typeScript 類(lèi)型的時(shí)候,它是有特殊含義的 - 它對(duì)應(yīng)是(Object.prototype.__proto__)=null在 js 原型鏈上的地位,它被視為所有的對(duì)象類(lèi)型的基類(lèi)。
  • array 的字面量形式的子類(lèi)型就是tuple,function 的字面量形式的子類(lèi)型就是函數(shù)表達(dá)式類(lèi)型。tuple函數(shù)表達(dá)式類(lèi)型都被囊括到 字面量類(lèi)型中去。

現(xiàn)在我們用這個(gè)新的心智模型去理解一下 示例 2-1 報(bào)錯(cuò)的地方:

  • type Test1_1 = UselessType<number|string> 之所以報(bào)錯(cuò),是因?yàn)樵陬?lèi)型約束中,如果 extends前面的類(lèi)型是聯(lián)合類(lèi)型,那么要想滿足類(lèi)型約束,則聯(lián)合類(lèi)型的每一個(gè)成員都必須滿足類(lèi)型約束才行。這就是所謂的「聯(lián)合類(lèi)型的分配律」。顯然,string extends number 是不成立的,所以整個(gè)聯(lián)合類(lèi)型就不滿足類(lèi)型約束;
  • 對(duì)于對(duì)象類(lèi)型的類(lèi)型 - 即強(qiáng)調(diào)由屬性和方法所組成的集合類(lèi)型,我們需要先用面向?qū)ο蟮母拍顏?lái)確定兩個(gè)類(lèi)型中,誰(shuí)是子類(lèi),誰(shuí)是父類(lèi)。這里的判斷方法是 - 如果 A 類(lèi)型相比 B 類(lèi)型多出了一些屬性/方法的話(這也同時(shí)意味著 B 類(lèi)型擁有的屬性或者方法,A 類(lèi)型也必須要有),那么 A 類(lèi)型就是父類(lèi),B 類(lèi)型就是子類(lèi)。然后,我們?cè)俎D(zhuǎn)換到子類(lèi)型和父類(lèi)型的概念上來(lái) - 父類(lèi)就是「父類(lèi)型」,子類(lèi)就是「子類(lèi)型」。
    • type Test2_1 = UselessType2<{a:1}> 之所以報(bào)錯(cuò),是因?yàn)?code>{a:1}是{a:1, b:2}的父類(lèi)型,所以是不能賦值給{a:1, b:2};
    • {[key:string]: any}并不能成為 {a:1, b:2} 的子類(lèi)型,因?yàn)?,父?lèi)型有的屬性/方法,子類(lèi)型必須顯式地?fù)碛小?code>{[key:string]: any}沒(méi)有顯式地?fù)碛?,所以,它不?{a:1, b:2}的子類(lèi)型,而是它的父類(lèi)型。
    • type Test3 = UselessType3<{name: '鯊叔'}>type Test3_2 = UselessType3<BaseClass> 報(bào)錯(cuò)的原因也是因?yàn)橐驗(yàn)槿鄙倭讼鄳?yīng)的屬性/方法,所以,它們都不是SubClass的子類(lèi)型。

到這里,我們算是剖析完畢。下面總結(jié)一下。

  • 當(dāng) extends 緊跟在泛型形參后面時(shí),它是在表達(dá)「類(lèi)型約束」的語(yǔ)義;
  • AType extends BType 中,只有 ATypeBType 的子類(lèi)型,ts 通過(guò)類(lèi)型約束的檢驗(yàn);
  • 面對(duì)兩個(gè) typeScript 類(lèi)型,到底誰(shuí)是誰(shuí)的子類(lèi)型,我們可以根據(jù)上面給出的 「ts 類(lèi)型層級(jí)關(guān)系圖」來(lái)判斷。而對(duì)于一些充滿迷惑的邊緣用例,死記硬背即可。

extends 與條件類(lèi)型

眾所周知,ts 中的條件類(lèi)型就是 js 世界里面的「三元表達(dá)式」。只不過(guò),相比值世界里面的三元表達(dá)式最終被計(jì)算出一個(gè)「值」,ts 的三元表達(dá)式最終計(jì)算出的是「類(lèi)型」。下面,我們先來(lái)復(fù)習(xí)一下它的語(yǔ)法:

AType extends BType ?  CType :  DType

在這里,extends 關(guān)鍵字出現(xiàn)在三元表達(dá)的第一個(gè)子句中。按照我們對(duì) js 三元表達(dá)式的理解,我們對(duì) typeScript 的三元表達(dá)式的理解應(yīng)該是相似的:如果 AType extends BType 為邏輯真值,那么整個(gè)表達(dá)式就返回 CType,否則的話就返回DType。作為過(guò)來(lái)人,只能說(shuō),大部分情況是這樣的,在幾個(gè)邊緣 case 里面,ts 的表現(xiàn)讓你大跌眼鏡,后面會(huì)介紹。

跟 js 的三元表達(dá)式支持嵌套一樣,ts 的三元表達(dá)式也支持嵌套,即下面也是合法的語(yǔ)法:

AType extends BType ?  (CType extends DType ? EType : FType) : (GType extends HType ? IType : JType)

到這里,我們已經(jīng)看到了 typeScript 的類(lèi)型編程世界的大門(mén)了。因?yàn)?,三元表達(dá)式本質(zhì)就是條件-分支語(yǔ)句,而后者就是邏輯編輯世界的最基本的要素了。而在我們進(jìn)入 typeScript 的類(lèi)型編程世界之前,我們首要搞清楚的是,AType extends BType何時(shí)是邏輯上的真值。

幸運(yùn)的是,我們可以復(fù)用「extends 與類(lèi)型約束」上面所產(chǎn)出的心智模型。簡(jiǎn)而言之,如果 ATypeBType 的子類(lèi)型,那么代碼執(zhí)行就是進(jìn)入第一個(gè)條件分支語(yǔ)句,否則就會(huì)進(jìn)入第二個(gè)條件分支語(yǔ)句。

上面這句話再加上「ts 類(lèi)型層級(jí)關(guān)系圖」,我們幾乎可以理解AType extends BType 99% 的語(yǔ)義。還剩下 1% 就是那些違背正常人直覺(jué)的特性表現(xiàn)。下面我們重點(diǎn)說(shuō)說(shuō)這 1% 的特性表現(xiàn)。

extends 與 {}

我們開(kāi)門(mén)見(jiàn)山地問(wèn)吧:“請(qǐng)說(shuō)出下面代碼的運(yùn)行結(jié)果。”

type Test = 1 extends {} ? true : false // 請(qǐng)問(wèn) `Test` 類(lèi)型的值是什么?

如果你認(rèn)真地去領(lǐng)會(huì)上面給出的「ts 類(lèi)型層級(jí)關(guān)系圖」,我相信你已經(jīng)知道答案了。如果你是基于「鴨子辯型」的直觀理解去判斷,那么我相信你的答案是false。但是我的遺憾地告訴你,在 typeScript@4.9.4中,答案是true。這明顯是違背人類(lèi)直覺(jué)的。于是乎,你會(huì)有這么一個(gè)疑問(wèn):“字面量類(lèi)型 1{}類(lèi)型似乎牛馬不相及,既不形似,也不神似,它怎么可能是是「字面量空對(duì)象」的子類(lèi)型呢?”

好吧,就像我們?cè)谏弦还?jié)提過(guò)的,{}在 typeScript 中,不應(yīng)該被理解為字面量空對(duì)象。它是一個(gè)特殊存在。它是一切有值類(lèi)型的基類(lèi)。ts 對(duì)它這么定位,似乎也合理。因?yàn)楹魬?yīng)了一個(gè)事實(shí) - 在 js 中,一切都是對(duì)象 (字面量 1 在 js 引擎內(nèi)部也是會(huì)被包成一個(gè)對(duì)象 - Number()的實(shí)例)。

現(xiàn)在,你不妨拿別的各種類(lèi)型去測(cè)試一下它跟 {} 的關(guān)系,看看結(jié)果是不是跟我說(shuō)的一樣。最后,有一個(gè)注意點(diǎn)值的強(qiáng)調(diào)一下。假如我們忽略無(wú)處不在,似乎是百變星君的 any{} 的父類(lèi)型只有一個(gè) - unknown。不信,我們可以試一試:

type Test = unknown extends {} ? true : false // `Test` 類(lèi)型的值是 `false`

Test2 類(lèi)型的值是 false,從而證明了unknown{}的父類(lèi)型。

extends 與 any

也許你會(huì)覺(jué)得,extendsany 有什么好講得嘛。你上面不是說(shuō)了「any」既是所有類(lèi)型的子類(lèi)型,又是所有類(lèi)型的父類(lèi)型。所以,以下示例代碼得到的類(lèi)型一定是true:

type Test = any extends number ? true : false

額......在 typeScript@4.9.4 中, 結(jié)果似乎不是這樣的 - 上面示例代碼的運(yùn)行結(jié)果是boolean。這到底是怎么回事呢?這是因?yàn)?,?typeScript 的條件類(lèi)型中,當(dāng)any 出現(xiàn)在 extends 前面的時(shí)候,它是被視為一個(gè)聯(lián)合里類(lèi)型。這個(gè)聯(lián)合類(lèi)型有兩個(gè)成員,一個(gè)是extends 后面的類(lèi)型,一個(gè)非extends 后面的類(lèi)型。還是用上面的示例舉例子:

type Test = any extends number ? true : false
// 其實(shí)等同于
type Test = (number | non-number) extends number ? true : false
// 根據(jù)聯(lián)合類(lèi)型的分配率,展開(kāi)得到
type Test = (number extends number ? true : false) | (non-number extends number ? true : false)
          = true | false
          = boolean
// 不相信我?我們?cè)賮?lái)試一個(gè)例子:
type Test2 = any extends number ? 1 : 2
// 其實(shí)等同于
type Test2 = (number | non-number) extends number ? 1 : 2
// 根據(jù)聯(lián)合類(lèi)型的分配率,展開(kāi)得到
type Test = (number extends number ? 1 : 2) | (non-number extends number ? 1 : 2)
          = 1 | 2

也許你會(huì)問(wèn),如果把 any 放在后面呢?比如:

type Test = number extends any ? true : false

這種情況我們可以依據(jù) 「任意類(lèi)型都是any的子類(lèi)型」得到最終的結(jié)果是true。

關(guān)于 extends 與 any 的運(yùn)算結(jié)果,總結(jié)一下,總共有兩種情況:

  • any extends SomeType(非 any 類(lèi)型) ? AType : BType 的結(jié)果是聯(lián)合類(lèi)型 AType | BType
  • SomeType(可以包含 any 類(lèi)型) extends any ? AType : BType 的結(jié)果是 AType

extends 與 never

在 typeScript 的三元表達(dá)式中,當(dāng) never 遇見(jiàn) extends,結(jié)果就變得很有意思了??梢該Q個(gè)角度說(shuō),是很奇怪。假設(shè),我現(xiàn)在要你實(shí)現(xiàn)一個(gè) typeScript utility 去判斷某個(gè)類(lèi)型(不考慮any)是否是never的時(shí)候,你可能會(huì)不假思索地在想:因?yàn)?never 是處在 typeScript 類(lèi)型層級(jí)的最底層,也就是說(shuō),除了它自己,沒(méi)有任何類(lèi)型是它的子類(lèi)型。所以答案肯定是這樣:

type IsNever<T> = T extends never ? true : false

然后,你信心滿滿地給泛型形參傳遞個(gè)never去測(cè)試,你發(fā)現(xiàn)結(jié)果是never,而不是true或者false:

type  Test = IsNever<never> // Test 的值為 `never`, 而不是我們期待的  `true`

再然后,你不甘心,你寫(xiě)下了下面的代碼去進(jìn)行再次測(cè)試:

type  Test = never extends never ? true : false // Test 的值為 `true`, 符合我們的預(yù)期

你會(huì)發(fā)現(xiàn),這次的結(jié)果卻是符合我們的預(yù)期的。此時(shí),你腦海里面肯定有千萬(wàn)匹草泥馬奔騰而過(guò)。是的,ts 類(lèi)型系統(tǒng)中,某些行為就是那么的匪夷所思。

對(duì)于這種違背直覺(jué)的特性表現(xiàn),當(dāng)前的解釋是:當(dāng) never 充當(dāng)實(shí)參去實(shí)例化泛型形參的時(shí)候,它被看作沒(méi)有任何成員的聯(lián)合類(lèi)型。當(dāng) tsc 對(duì)沒(méi)有成員的聯(lián)合類(lèi)型執(zhí)行分配律時(shí),tsc 認(rèn)為這么做沒(méi)有任何意義,所以就不執(zhí)行這段代碼,直接返回 never

那正確的實(shí)現(xiàn)方式是什么?。渴沁@個(gè):

type IsNever<T> = [T] extends [never] ? true : false

原理是什么???答曰:「通過(guò)放入 tuple 中,消除了聯(lián)合類(lèi)型碰上 extends 時(shí)所產(chǎn)生的分配律」。

extends 與 聯(lián)合類(lèi)型

上面也提到了,在 typeScript 三元表達(dá)中,當(dāng) extends 前面的類(lèi)型是聯(lián)合類(lèi)型的時(shí)候,ts 就會(huì)產(chǎn)生類(lèi)似于「乘法分配律」行為表現(xiàn)。具體可以用下面的示例來(lái)表述:

type Test = (AType | BType) extends SomeType ? 'yes' : 'no'
          =  (AType extends SomeType ? 'yes' : 'no') | (BType extends SomeType ? 'yes' : 'no')

我們?cè)賮?lái)看看「乘法分配律」:(a+b)*c = a*c + b*c。對(duì)比一下,我們就是知道,三元表達(dá)式中的 |就是乘法分配律中的 +, 三元表達(dá)式中的 extends 就是乘法分配律中的 *。下面是表達(dá)這種類(lèi)比的偽代碼:

type Test = (AType + BType) * (SomeType ? 'yes' : 'no')
          =  AType * (SomeType ? 'yes' : 'no') + BType * (SomeType ? 'yes' : 'no')

另外,還有一個(gè)很重要的特性是,當(dāng)聯(lián)合類(lèi)型的泛型形參的出現(xiàn)在三元表達(dá)式中的真值或者假值分支語(yǔ)句中,它指代的是正在遍歷的聯(lián)合類(lèi)型的成員元素。在編程世界里面,利用聯(lián)合類(lèi)型的這個(gè)特性,我們可以遍歷聯(lián)合類(lèi)型的所有成員類(lèi)型。比如,ts 內(nèi)置的 utility Exclude<T,U> 就是利用這種特性所實(shí)現(xiàn)的:

type  MyExclude<T,U>= T extends U ? never :  T; // 第二個(gè)條件分支語(yǔ)句中, T 指代的是正在遍歷的成員元素
type Test = MyExclude<'a'|'b'|'c', 'a'> // 'b'|'c'

在上面的實(shí)現(xiàn)中,在你將類(lèi)型實(shí)參代入到三元表達(dá)式中,對(duì)于第二個(gè)條件分支的T 記得要理解為'a'|'b'|'c'的各個(gè)成員元素,而不是理解為完整的聯(lián)合類(lèi)型。

有時(shí)候,聯(lián)合類(lèi)型的這種分配律不是我們想要的。那么,我們?cè)撛趺聪@種特性呢?其實(shí)上面在講「extends 與 never 」的時(shí)候也提到了。那就是,用方括號(hào)[]包住 extends 前后的兩個(gè)類(lèi)型參數(shù)。此時(shí),兩個(gè)條件分支里面的聯(lián)合類(lèi)型參數(shù)在實(shí)例化時(shí)候的值將會(huì)跟 extends 子句里面的是一樣的。

// 具有分配律的寫(xiě)法
type ToArray<Type> = Type extends any ? Type[] : never; //
type StrArrOrNumArr = ToArray<string | number>; // 結(jié)果是:`string[] | number[]`
// 消除分配律的寫(xiě)法
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr2 = ToArray<string | number>; // 結(jié)果是:`(string | number)[]`

也許你會(huì)覺(jué)得 string[] | number[](string | number)[]是一樣的,我只能說(shuō):“客官,要不您再仔細(xì)瞧瞧?”。

extends 判斷類(lèi)型嚴(yán)格相等

在 typeScript 的類(lèi)型編程世界里面,很多時(shí)候我們需要判斷兩個(gè)類(lèi)型是否是一模一樣的,即這里所說(shuō)的「嚴(yán)格相等」。如果讓你去實(shí)現(xiàn)這個(gè) utility 的話,你會(huì)怎么做呢?我相信,不少人會(huì)跟我一樣,不假思索地寫(xiě)下了下面的答案:

type  IsEquals<T,U>= T extends U ? U extends T ? true : false :  false

這個(gè)答案似乎是邏輯正確的。因?yàn)?,如果只有自己才可能既是自己的子?lèi)型也是自己的父類(lèi)型。然后,我們用很多測(cè)試用例去測(cè),似乎結(jié)果也都符合我們的預(yù)期。直到我們碰到下面的邊緣用例:

type  Test1= IsEquals<never,never> // 期待結(jié)果:true,實(shí)際結(jié)果: never
type  Test2= IsEquals<1,any> // 期待結(jié)果:false,實(shí)際結(jié)果: boolean
type  Test3= IsEquals<{readonly a: 1},{a:1}> // 期待結(jié)果:false,實(shí)際結(jié)果: true

沒(méi)辦法, typeScript 的類(lèi)型系統(tǒng)有太多的違背常識(shí)的設(shè)計(jì)與實(shí)現(xiàn)了。如果還是沿用上面的思路,即使你把上面的特定用例修復(fù)好了,但是說(shuō)不定還有其他的邊緣用例躲在某個(gè)陰暗的角度等著你。所以,對(duì)于「如何判斷兩個(gè) typeScript 類(lèi)型是嚴(yán)格相等」的這個(gè)問(wèn)題上,目前社區(qū)里面從 typeScript 實(shí)現(xiàn)源碼角度上給出了一個(gè)終極答案:

type IsEquals<X, Y> =
      (<T>() => (T extends  X ? 1 : 2)) extends
      (<T>() => (T extends  Y ? 1 : 2))
      ? true
      : false;

目前我還沒(méi)理解這個(gè)終極答案為什么是行之有效的,但是從測(cè)試結(jié)果來(lái)看,它確實(shí)是 work 的,并且被大家所公認(rèn)。所以,目前為止,對(duì)于這個(gè)實(shí)現(xiàn)只能是死記硬背了。

extends 與類(lèi)型推導(dǎo)

type Test<A> = A extends SomeShape ? 第一個(gè)條件分支 : 第二支條件分支

當(dāng) typeScript 的三元表達(dá)式遇見(jiàn)類(lèi)型推導(dǎo)infer SomeType, 在語(yǔ)法上是有硬性要求的:

  • infer 只能出現(xiàn)在 extends 子句中,并且只能出現(xiàn)在 extends 關(guān)鍵字后面
  • 緊跟在 infer 后面所聲明的類(lèi)型形參只能在三元表達(dá)式的第一個(gè)條件分支(即,真值分支語(yǔ)句)中使用

除了語(yǔ)法上有硬性要求,我們也要正確理解 extends 遇見(jiàn)類(lèi)型推導(dǎo)的語(yǔ)義。在這個(gè)上下文中,infer SomeType 更像是具有某種結(jié)構(gòu)的類(lèi)型的占位符。SomeShape 中可以通過(guò) infer 來(lái)聲明多個(gè)類(lèi)型形參,它們與一些已知的類(lèi)型值共同組成了一個(gè)代表具有如此形態(tài)的SomeShape 。而 A extends SomeShape 是我們開(kāi)發(fā)者在表達(dá):「tsc,請(qǐng)按照顧我所聲明的這種結(jié)構(gòu)去幫我推導(dǎo)得出各個(gè)泛型形參在運(yùn)行時(shí)的值,以便供我進(jìn)一步消費(fèi)這些值」,而 tsc 會(huì)說(shuō):「好的,我盡我所能」。

「tsc 會(huì)盡我所能地去推導(dǎo)出具體的類(lèi)型值」這句話的背后蘊(yùn)含著不少的 typeScript 未在文檔上交代的行為表現(xiàn)。比如,當(dāng)類(lèi)型形參與類(lèi)型值共同出現(xiàn)在「數(shù)組」,「字符串」等可遍歷的類(lèi)型中,tsc 會(huì)產(chǎn)生類(lèi)似于「子串/子數(shù)組匹配」的行為表現(xiàn) - 也就是說(shuō),tsc 會(huì)以非貪婪匹配模式遍歷整個(gè)數(shù)組/字符串進(jìn)行子串/數(shù)組匹配,直到匹配到最小的子串/子數(shù)組為止。這個(gè)結(jié)果,就是我們類(lèi)型推導(dǎo)的泛型形參在運(yùn)行時(shí)的值。

舉個(gè)例子,下面的代碼是實(shí)現(xiàn)一個(gè)ReplaceOnce 類(lèi)型 utility 代碼:

type ReplaceOnce<
  S extends string,
  From extends string,
  To extends string
> = From extends ""
  ? S
  : S extends `${infer Left}${From}${infer Right}`
  ? `${Left}${To}${Right}`
  : S
  “”
type Test = Replace<"foobarbar", "bar", ""> // 結(jié)果是:“foobar”

tsc 在執(zhí)行上面的這行代碼「S extends ${infer Left}${From}${infer Right}」的時(shí)候,背后做了一個(gè)從左到右的「子串匹配」行為,直到匹配到所傳遞進(jìn)來(lái)的子串From為止。這個(gè)時(shí)候,也是 resolve 出形參LeftRight具體值的時(shí)候。

以上示例很好的表達(dá)出我想要表達(dá)的「當(dāng)extends 跟類(lèi)型推導(dǎo)結(jié)合到一塊所產(chǎn)生的一些微妙且未見(jiàn)諸于官方文檔的行為表現(xiàn)」。在 typeScript 高級(jí)類(lèi)型編程中,善于利用這一點(diǎn)能夠幫助我們?nèi)ソ鉀Q很多「子串/子數(shù)組匹配」相關(guān)的問(wèn)題。

總結(jié)

在 typeScript 在不同的上下文中,extends 有以下幾個(gè)語(yǔ)義:

  • 用于表達(dá)類(lèi)型組合;
  • 用于表達(dá)面向?qū)ο笾小割?lèi)」的繼承
  • 用于表達(dá)泛型的類(lèi)型約束;
  • 在條件類(lèi)型(conditional type)中,充當(dāng)類(lèi)型表達(dá)式,用于求值。

最值得注意的是,extends在條件類(lèi)型中與其他幾個(gè)特殊類(lèi)型結(jié)合所產(chǎn)生的特殊語(yǔ)義。幾個(gè)特殊類(lèi)型是:

  • {}
  • any
  • never
  • 聯(lián)合類(lèi)型

參考資料

以上就是一文詳解typeScript的extends關(guān)鍵字的詳細(xì)內(nèi)容,更多關(guān)于typeScript extends關(guān)鍵字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

荆门市| 安塞县| 厦门市| 和顺县| 谷城县| 榆中县| 科尔| 武穴市| 盐边县| 澄江县| 旬阳县| 永寿县| 新巴尔虎左旗| 乐至县| 区。| 新化县| 利津县| 嘉善县| 家居| 湄潭县| 峨眉山市| 巴林左旗| 天峻县| 黔西县| 黄浦区| 卓尼县| 金堂县| 布拖县| 顺义区| 湘潭县| 鄯善县| 怀远县| 普安县| 景宁| 霞浦县| 阿巴嘎旗| 德昌县| 景谷| 合水县| 吴川市| 山东|