TypeScript中extends的正確打開方式詳解
前言
最近完整地看了一遍TypeScript
的官方文檔,發(fā)現(xiàn)文檔中有一些知識(shí)點(diǎn)沒有專門講解到,或者是講解了但卻十分難以理解,因此就有了這一系列的文章,我將對(duì)沒有講解到的或者是我認(rèn)為難以理解的知識(shí)點(diǎn)進(jìn)行補(bǔ)充講解,希望能給您帶來一點(diǎn)幫助。
tips:配合官方文檔食用更佳
這是本系列的第二篇TypeScript中extends的正確打開方式,在TypeScript
中我們經(jīng)常見到extends
這一關(guān)鍵字,我們可能第一時(shí)間想到的就是繼承,但除了繼承它其實(shí)還有其他的用法,接下來讓我們來一一獲得extends
的正確打開方式。
extends第一式:繼承
作為眾人皆知的extends
關(guān)鍵字,其最出名的使用方式便是繼承。
類繼承類
首先讓我們來用extends
實(shí)現(xiàn)一下類的繼承。
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(); //別看我只是一只羊,羊兒的聰明難以想象~
首先我們定義了一個(gè)Animal
類,該類有name
屬性以及eat
方法。然后又定義了一個(gè)繼承Animal
類的Sheep
類,該類在父類name
屬性以及eat
方法基礎(chǔ)上又新增了一個(gè)miemie
方法。
接口繼承接口
extends
不僅能夠用于類與類之間的繼承上,還能夠用于接口與接口之間的繼承。接下來我們來實(shí)現(xiàn)一下接口之間的繼承。
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(); //別看我只是一只羊,羊兒的聰明難以想象~
我們定義了一個(gè)IAnimal
接口,然后用通過extends
繼承IAnimal
定義了ISheep
接口,則實(shí)現(xiàn)ISheep
接口的變量lanyangyang
必須要有父接口的name
屬性以及實(shí)現(xiàn)eat
方法,并且還要實(shí)現(xiàn)本身的miemie
方法。
現(xiàn)在我們通過extends
實(shí)現(xiàn)了類與類之間的繼承、接口與接口之間的繼承,那么類與接口之間是否能互相繼承呢?答案是可以。
接口繼承類
首先我們使用extends
來實(shí)現(xiàn)接口繼承類。
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(); //別看我只是一只羊,羊兒的聰明難以想象~
在接口繼承類時(shí),可以把類看作一個(gè)接口,但是類中的靜態(tài)方法是不會(huì)繼承過來的。我們?cè)趯?shí)現(xiàn)ISheep
接口的變量lanyangyang
必須要有類Animal
的name
屬性以及實(shí)現(xiàn)eat
方法,并且還要實(shí)現(xiàn)本身的miemie
方法。但是我們不必實(shí)現(xiàn)類Animal
的靜態(tài)方法run
。
是不是覺得還會(huì)有下一個(gè)標(biāo)題類繼承接口
,對(duì)不起,這個(gè)真沒有!類繼承接口使用的關(guān)鍵字變成了implements
。
extends第二式:三元表達(dá)式條件判斷
extends
還有一個(gè)比較常見的用法就是在三元表達(dá)式中進(jìn)行條件判斷,即判斷一個(gè)類型是否可以分配給另一個(gè)類型。這里根據(jù)三元表達(dá)式中是否存在泛型判斷結(jié)果還不一致,首先我們介紹普通的三元表達(dá)式條件判斷。
普通的三元表達(dá)式條件判斷
帶有extends
的三元表達(dá)式如下:
type TypeRes=Type1 extends Type2? Type3: Type4;
這里表達(dá)的意思就是如果類型Type1
可被分配給類型Type2
,則類型TypeRes
取Type3
,否則取Type4
。那怎么理解類型Type1
可被分配給類型Type2
呢??
我們可以這樣理解:類型為Type1
的值可被賦值給類型為Type2
的變量??梢跃唧w分為一下幾種情況:
Type1
和Type2
為同一種類型。Type1
是Type2
的子類型。Type2
類型兼容類型Type1
。 接下來我們分情況進(jìn)行驗(yàn)證。
情況一:Type1
和Type2
為同一種類型。
type Type1=string; type Type2=Type1; type Type3=true; type Type4=false; type TypeRes=Type1 extends Type2? Type3: Type4; // true
這里Type1
和Type2
為同一種類型。因此Type1
可被分配給Type2
。因此TypeRes
類型最后取為true
。
情況二:Type1
是Type2
的子類型。
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
,即Type1
是Type2
的子類型。因此Type1
可被分配給Type2
。因此TypeRes
類型最后取為true
。
情況三: Type2
類型兼容類型Type1
。
首先還是拋出一個(gè)問題,什么是類型兼容
??這個(gè)問題可以從官方文檔中得到答案,大家可以戳類型兼容性詳細(xì)了解!
所謂 Type2
類型兼容類型Type1
,指得就是Type1
類型的值可被賦值給類型為Type2
的變量。 舉個(gè)栗子:
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
。
再舉個(gè)栗子,以函數(shù)的兼容性為例,
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
當(dāng)函數(shù)參數(shù)不同時(shí),看Type2
是否兼容Type1
,就要看Type1
的每個(gè)參數(shù)必須能在Type2
里找到對(duì)應(yīng)類型的參數(shù)。 注意的是參數(shù)的名字相同與否無所謂,只看它們的類型。
這里Type1
第一個(gè)number
類型的參數(shù)是可以在Type2
中找到,即Type1
類型的函數(shù)可被賦值給類型為Type2
的變量。
let fn1:Type1=(a:number)=>{} let kenny2:Type2=fn1; // no Error
因此TypeRes
類型最后取為true
。
帶有泛型的三元表達(dá)式條件判斷
我們先來一個(gè)舉一個(gè)不帶泛型的栗子,大家可以想想結(jié)果是什么。
type Type1=string|number; type Type2=string; type Type3=true; type Type4=false; type TypeRes=Type1 extends Type2? Type3: Type4;
沒錯(cuò),由于Type1
是Type2
的父類型,因此Type1
是不可分配給Type2
的,因此TypeRes
類型最后取為false
。
但當(dāng)我們加上泛型之后呢,再來看一個(gè)栗子。
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
。這是為什么呢?
原來再使用泛型時(shí),若extends左側(cè)的泛型具體取為一個(gè)聯(lián)合類型時(shí),就會(huì)把聯(lián)合類型中的類型拆開,分別帶入到條件判斷式中進(jìn)行判斷,最后把結(jié)果再進(jìn)行聯(lián)合。上述的栗子中結(jié)果可以這么來看,
(string extends string?true:false)|(number extends string?true:false) true | false boolean
在高級(jí)類型中有很多類型實(shí)現(xiàn)便用到了這一特性。
比如Exclude<T, U>
-- 從T
中剔除可以賦值給U
的類型。
type T1 = "a" | "b" | "c" | "d"; type T2 = "a" | "c" | "f" type ExcludeT1T2=Exclude<T1,T2> //"b"|"d"
該類型的類型實(shí)現(xiàn)為
type Exclude<T, U> = T extends U ? never : T;
當(dāng)T
為聯(lián)合類型時(shí),會(huì)自動(dòng)分發(fā)條件,對(duì)T
中的所有類型進(jìn)行遍歷,判斷其是否可以分配給類型U
,如果是的話便返回never
類型,否則返回其原來的類型。最后再將其進(jìn)行聯(lián)合得到一個(gè)結(jié)果聯(lián)合類型。
由于never
類型與其他類型聯(lián)合最終得到的還是其他類型,因此便可以從類型T中剔除掉可以賦給U的類型。
那有沒有辦法讓泛型三元表達(dá)式中extends
和普通的extends
作用相同?有!只需要給泛型加一個(gè)[]
。栗子如下:
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第三式:泛型約束
首先我們來回答一下什么是泛型?簡(jiǎn)單來說,泛型就是一種類型變量,普通的變量代表一個(gè)任意的值,而不是一個(gè)特定的值,我們可以把任何值賦給變量,而類型變量代表一個(gè)任意的類型,而不是一個(gè)特定的類型,我們可以把任何類型賦給類型變量。它是一種特殊的變量,只用于表示類型而不是值。
那如果我們不想讓泛型表示任意類型時(shí),該怎么辦?這時(shí)我們就可以使用extends
對(duì)泛型進(jìn)行約束,讓泛型表示滿足一定條件的類型。接下來,我們使用extends
進(jìn)行泛型的約束。
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}正在奔跑`)}; } ) // 懶羊羊正在吃青草蛋糕 //別看我只是一只羊,羊兒的聰明難以想象~
這里我們便對(duì)泛型T
進(jìn)行了約束,其必須至少要擁有ISheep
的name
屬性及eat
、miemie
方法,另外T
中若有其他的屬性及方法,則不作限制。這里我們便通過extends
對(duì)泛型T
進(jìn)行了約束。
其實(shí)泛型約束中的extends
也是起到了三元表達(dá)式中類型分配的作用,其中T extends ISheep
表示泛型T
必須可以分配給類型Isheep
。
以上就是TypeScript中extends的正確打開方式詳解的詳細(xì)內(nèi)容,更多關(guān)于TypeScript extends打開方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解微信小程序如何實(shí)現(xiàn)類似ChatGPT的流式傳輸
這篇文章主要為大家介紹了微信小程序如何實(shí)現(xiàn)類似ChatGPT的流式傳輸示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Babel?插件開發(fā)&訪問節(jié)點(diǎn)實(shí)例詳解
這篇文章主要為答案及介紹了Babel?插件開發(fā)&訪問節(jié)點(diǎn)實(shí)例詳解,整理一下?Babel?插件開發(fā)時(shí)用得到的轉(zhuǎn)換操作相關(guān)的?API,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08微信小程序 form組件詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了微信小程序 form組件詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-01-01