首頁 > 軟體

TypeScript中extends的正確開啟方式詳解

2022-08-22 14:02:14

前言

最近完整地看了一遍TypeScript的官方檔案,發現檔案中有一些知識點沒有專門講解到,或者是講解了但卻十分難以理解,因此就有了這一系列的文章,我將對沒有講解到的或者是我認為難以理解的知識點進行補充講解,希望能給您帶來一點幫助。

tips:配合官方檔案食用更佳

這是本系列的第二篇TypeScript中extends的正確開啟方式,在TypeScript中我們經常見到extends這一關鍵字,我們可能第一時間想到的就是繼承,但除了繼承它其實還有其他的用法,接下來讓我們來一一獲得extends的正確開啟方式。

extends第一式:繼承

作為眾人皆知的extends關鍵字,其最出名的使用方式便是繼承。

類繼承類

首先讓我們來用extends實現一下類的繼承。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
}
class Sheep extends Animal {
  constructor(name: string) {
    super(name);
  }
  miemie() {
    console.log("別看我只是一隻羊,羊兒的聰明難以想象~");
  }
}
let lanyangyang = new Sheep("懶羊羊");
lanyangyang.eat("青草蛋糕");
// 懶羊羊正在吃青草蛋糕
lanyangyang.miemie();
//別看我只是一隻羊,羊兒的聰明難以想象~

首先我們定義了一個Animal類,該類有name屬性以及eat方法。然後又定義了一個繼承Animal類的Sheep類,該類在父類別name屬性以及eat方法基礎上又新增了一個miemie方法。

介面繼承介面

extends不僅能夠用於類與類之間的繼承上,還能夠用於介面與介面之間的繼承。接下來我們來實現一下介面之間的繼承。

interface IAnimal{
  name:string;
  eat:(food:string)=>void;
}
interface ISheep extends IAnimal{
  miemie:()=>void;
}
let lanyangyang:ISheep={
  name:'懶羊羊',
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("別看我只是一隻羊,羊兒的聰明難以想象~");
  }   
}
lanyangyang.eat("青草蛋糕");
// 懶羊羊正在吃青草蛋糕
lanyangyang.miemie();
//別看我只是一隻羊,羊兒的聰明難以想象~

我們定義了一個IAnimal介面,然後用通過extends繼承IAnimal定義了ISheep介面,則實現ISheep介面的變數lanyangyang必須要有父介面的name屬性以及實現eat方法,並且還要實現本身的miemie方法。

現在我們通過extends實現了類與類之間的繼承、介面與介面之間的繼承,那麼類與介面之間是否能互相繼承呢?答案是可以。

介面繼承類

首先我們使用extends來實現介面繼承類。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
  static run(){
    console.log(`${this.name} is running`)
  }
}
interface ISheep extends Animal{
  miemie:()=>void;
}
let lanyangyang:ISheep={
  name:'懶羊羊',
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("別看我只是一隻羊,羊兒的聰明難以想象~");
  }   
}
lanyangyang.eat("青草蛋糕");
// 懶羊羊正在吃青草蛋糕
lanyangyang.miemie();
//別看我只是一隻羊,羊兒的聰明難以想象~

在介面繼承類時,可以把類看作一個介面,但是類中的靜態方法是不會繼承過來的。我們在實現ISheep介面的變數lanyangyang必須要有類Animalname屬性以及實現eat方法,並且還要實現本身的miemie方法。但是我們不必實現類Animal的靜態方法run

是不是覺得還會有下一個標題類繼承介面,對不起,這個真沒有!類繼承介面使用的關鍵字變成了implements

extends第二式:三元表示式條件判斷

extends還有一個比較常見的用法就是在三元表示式中進行條件判斷,即判斷一個型別是否可以分配給另一個型別。這裡根據三元表示式中是否存在泛型判斷結果還不一致,首先我們介紹普通的三元表示式條件判斷。

普通的三元表示式條件判斷

帶有extends的三元表示式如下:

type TypeRes=Type1 extends Type2? Type3: Type4;

這裡表達的意思就是如果型別Type1可被分配給型別Type2,則型別TypeResType3,否則取Type4。那怎麼理解型別Type1可被分配給型別Type2呢??

我們可以這樣理解:型別為Type1的值可被賦值給型別為Type2的變數。可以具體分為一下幾種情況:

  • Type1Type2為同一種型別。
  • Type1Type2的子型別。
  • Type2型別相容型別Type1。 接下來我們分情況進行驗證。

情況一:Type1Type2為同一種型別。

type Type1=string;
type Type2=Type1;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

這裡Type1Type2為同一種型別。因此Type1可被分配給Type2。因此TypeRes型別最後取為true

情況二:Type1Type2的子型別。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
}
class Sheep extends Animal {
  constructor(name: string) {
    super(name);
  }
  miemie() {
    console.log("別看我只是一隻羊,羊兒的聰明難以想象~");
  }
}
type Type1=Sheep;
type Type2=Animal;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

這裡Sheep類繼承自Animal,即Type1Type2的子型別。因此Type1可被分配給Type2。因此TypeRes型別最後取為true

情況三: Type2型別相容型別Type1

首先還是丟擲一個問題,什麼是型別相容??這個問題可以從官方檔案中得到答案,大家可以戳型別相容性詳細瞭解!

所謂 Type2型別相容型別Type1,指得就是Type1型別的值可被賦值給型別為Type2的變數。 舉個栗子:

type Type1={
  name:string;
  age:number;
  gender:string;
}
type Type2={
  name:string;
  age:number;
}
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

由於型別Type1擁有至少與Type2相同的屬性,因此Type2是相容Type1的。也就是說Type1型別的值可被賦值給型別為Type2的變數。

let kenny1:Type1={
  name:'kenny',
  age:26,
  gender:'male'
}
let kenny2:Type2=kenny;
// no Error

因此TypeRes型別最後取為true

再舉個栗子,以函數的相容性為例,

type Type1=(a:number)=>void;
type Type2=(a:number,b:string)=>void;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

當函數引數不同時,看Type2是否相容Type1,就要看Type1的每個引數必須能在Type2裡找到對應型別的引數。 注意的是引數的名字相同與否無所謂,只看它們的型別。

這裡Type1第一個number型別的引數是可以在Type2中找到,即Type1型別的函數可被賦值給型別為Type2的變數。

let fn1:Type1=(a:number)=>{}
let kenny2:Type2=fn1;
// no Error

因此TypeRes型別最後取為true

帶有泛型的三元表示式條件判斷

我們先來一個舉一個不帶泛型的栗子,大家可以想想結果是什麼。

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;

沒錯,由於Type1Type2的父類別型,因此Type1是不可分配給Type2的,因此TypeRes型別最後取為false

但當我們加上泛型之後呢,再來看一個栗子。

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type Type5<T>=T extends Type2? Type3: Type4;
type TypeRes<Type1>
// boolean

這裡TypeRes型別最後就不是false了,而變成boolean。這是為什麼呢?

原來再使用泛型時,若extends左側的泛型具體取為一個聯合型別時,就會把聯合型別中的型別拆開,分別帶入到條件判斷式中進行判斷,最後把結果再進行聯合。上述的栗子中結果可以這麼來看,

(string extends string?true:false)|(number extends string?true:false)
true | false
boolean

在高階型別中有很多型別實現便用到了這一特性。

比如Exclude<T, U> -- 從T中剔除可以賦值給U的型別。

type T1 = "a" | "b" | "c" | "d";
type T2 = "a" | "c" | "f"
type ExcludeT1T2=Exclude<T1,T2> //"b"|"d"

該型別的型別實現為

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

T為聯合型別時,會自動分發條件,對T中的所有型別進行遍歷,判斷其是否可以分配給型別U,如果是的話便返回never型別,否則返回其原來的型別。最後再將其進行聯合得到一個結果聯合型別。

由於never型別與其他型別聯合最終得到的還是其他型別,因此便可以從型別T中剔除掉可以賦給U的型別。

那有沒有辦法讓泛型三元表示式中extends和普通的extends作用相同?有!只需要給泛型加一個[]。栗子如下:

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type Type5<T>=[T] extends Type2? Type3: Type4;
type TypeRes<Type1>
// false

extends第三式:泛型約束

首先我們來回答一下什麼是泛型?簡單來說,泛型就是一種型別變數,普通的變數代表一個任意的值,而不是一個特定的值,我們可以把任何值賦給變數,而型別變數代表一個任意的型別,而不是一個特定的型別,我們可以把任何型別賦給型別變數。它是一種特殊的變數,只用於表示型別而不是值。

那如果我們不想讓泛型表示任意型別時,該怎麼辦?這時我們就可以使用extends對泛型進行約束,讓泛型表示滿足一定條件的型別。接下來,我們使用extends進行泛型的約束。

interface ISheep{
  name:string;
  eat:(food:string)=>void;
  miemie:()=>void;
}
function eatAndMiemie<T extends ISheep>(sheep:T):void{
    sheep.eat("青草蛋糕");
    sheep.miemie();
}
eatAndMiemie(
{
  name: "懶羊羊",
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("別看我只是一隻羊,羊兒的聰明難以想象~");
  }   
  run() {console.log(`${this.name}正在奔跑`)};
  }
)
// 懶羊羊正在吃青草蛋糕
//別看我只是一隻羊,羊兒的聰明難以想象~

這裡我們便對泛型T進行了約束,其必須至少要擁有ISheepname屬性及eatmiemie方法,另外T中若有其他的屬性及方法,則不作限制。這裡我們便通過extends對泛型T進行了約束。

其實泛型約束中的extends也是起到了三元表示式中型別分配的作用,其中T extends ISheep表示泛型T必須可以分配給型別Isheep

以上就是TypeScript中extends的正確開啟方式詳解的詳細內容,更多關於TypeScript extends開啟方式的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com