Typescript中extends關(guān)鍵字的基本使用
前言
extends關(guān)鍵字在TS編程中出現(xiàn)的頻率挺高的,而且不同場景下代表的含義不一樣,特此總結(jié)一下:
表示繼承/拓展的含義
表示約束的含義
表示分配的含義
基本使用
extends
是 ts 里一個很常見的關(guān)鍵字,同時也是 es6 里引入的一個新的關(guān)鍵字。在 js 里,extends
一般和class
一起使用,例如:
- 繼承父類的方法和屬性
class Animal { kind = 'animal' constructor(kind){ this.kind = kind; } sayHello(){ console.log(`Hello, I am a ${this.kind}!`); } } class Dog extends Animal { constructor(kind){ super(kind) } bark(){ console.log('wang wang') } } const dog = new Dog('dog'); dog.name; // => 'dog' dog.sayHello(); // => Hello, I am a dog!
這里 Dog 繼承了父類的 sayHello 方法,因為可以在 Dog 實例 dog 上調(diào)用。
- 繼承某個類型
在 ts 里,extends
除了可以像 js 繼承值,還可以繼承/擴(kuò)展類型:
interface Animal { kind: string; } interface Dog extends Animal { bark(): void; } // Dog => { name: string; bark(): void }
泛型約束
在書寫泛型的時候,我們往往需要對類型參數(shù)作一定的限制,比如希望傳入的參數(shù)都有 name 屬性的數(shù)組我們可以這么寫:
function getCnames<T extends { name: string }>(entities: T[]):string[] { return entities.map(entity => entity.cname) }
這里extends
對傳入的參數(shù)作了一個限制,就是 entities 的每一項可以是一個對象,但是必須含有類型為string
的cname
屬性。再比如,redux 里 dispatch 一個 action,必須包含 type
屬性:
interface Dispatch<T extends { type: string }> { (action: T): T }
條件類型與高階類型
SomeType extends OtherType ? TrueType : FalseType;
When the type on
the left
of theextends
isassignable to the one on the right
, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).
extends
還有一大用途就是用來判斷一個類型是不是可以分配給另一個類型,這在寫高級類型的時候非常有用,舉個 ????:
type Human = { name: string; } type Duck = { name: string; } type Bool = Duck extends Human ? 'yes' : 'no'; // Bool => 'yes'
在 vscode 里或者 ts playground 里輸入這段代碼,你會發(fā)現(xiàn) Bool 的類型是'yes'
。這是因為 Human 和 Duck 的類型完全相同,或者說 Human 類型的一切約束條件,Duck 都具備;換言之,類型為 Human 的值可以分配給類型為 Duck 的值(分配成功的前提是,Duck里面得的類型得有一樣的)
,反之亦然。需要理解的是,這里A extends B
,是指類型A
可以分配給類型B
,而不是說類型A
是類型B
的子集。稍微擴(kuò)展下來詳細(xì)說明這個問題:
type Human = { name: string; occupation: string; } type Duck = { name: string; } type Bool = Duck extends Human ? 'yes' : 'no'; // Bool => 'no'
當(dāng)我們給Human
加上一個occupation
屬性,發(fā)現(xiàn)此時Bool
是'no'
,這是因為 Duck 沒有類型為string
的occupation
屬性,類型Duck
不滿足類型Human
的類型約束。因此,A extends B
,是指類型A
可以分配給
類型B
,而不是說類型A
是類型B
的子集,理解extends
在類型三元表達(dá)式里的用法非常重要。
繼續(xù)看示例
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的類型就是完全一樣的呢?
有興趣可以自己試一試,這里就直接給結(jié)論了
type P<T> = T extends 'x' ? string : number; type A3 = P<'x' | 'y'> // A3的類型是 string | number
是不是很反直覺?這個反直覺結(jié)果的原因就是所謂的分配條件類型(Distributive Conditional Types)
When conditional types act on a generic type, they become distributive when given a union type
這句話翻譯過來也還是看不懂,我直接上大白話了
對于使用extends關(guān)鍵字的條件類型(即上面的三元表達(dá)式類型),如果extends前面的參數(shù)是一個泛型類型,當(dāng)傳入該參數(shù)的是聯(lián)合類型,則使用分配律計算最終的結(jié)果。分配律是指,將聯(lián)合類型的聯(lián)合項拆成單項,分別代入條件類型,然后將每個單項代入得到的結(jié)果再聯(lián)合起來,得到最終的判斷結(jié)果。
If we plug a union type into ToArray, then the conditional type will be applied to each member of that union.
還是用上面的例子說明
type P<T> = T extends 'x' ? string : number; type A3 = P<'x' | 'y'> // A3的類型是 string | number
該例中,extends的前參為T,T是一個泛型參數(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
然后將每一項代入得到的結(jié)果聯(lián)合起來,得到string | number
總之,滿足兩個要點即可適用分配律:第一,參數(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并不是一個聯(lián)合類型,所以直接代入條件類型的定義即可,獲取的結(jié)果應(yīng)該和A1一直才對???
實際上,這里還是條件分配類型在起作用。never被認(rèn)為是空的聯(lián)合類型,也就是說,沒有聯(lián)合項的聯(lián)合類型,所以還是滿足上面的分配律,然而因為沒有聯(lián)合項可以分配,所以P<T>
的表達(dá)式其實根本就沒有執(zhí)行,所以A2的定義也就類似于永遠(yuǎn)沒有返回的函數(shù)一樣,是never類型的。
- 防止條件判斷中的分配
type P<T> = [T] extends ['x'] ? string : number; type A1 = P<'x' | 'y'> // number type A2 = P<never> // string
在條件判斷類型的定義中,將泛型參數(shù)使用[]
括起來,即可阻斷條件判斷類型的分配,此時,傳入?yún)?shù)T的類型將被當(dāng)做一個整體,不再分配。
在高級類型中的應(yīng)用
- Exclude
Exclude
是TS中的一個高級類型,其作用是從第一個聯(lián)合類型參數(shù)中,將第二個聯(lián)合類型中出現(xiàn)的聯(lián)合項全部排除,只留下沒有出現(xiàn)過的參數(shù)。
示例:
type A = Exclude<'key1' | 'key2', 'key2'> // 'key1'
Exclude的定義是
type Exclude<T, U> = T extends U ? never : T
這個定義就利用了條件類型中的分配原則,來嘗試將實例拆開看看發(fā)生了什么:
type A = `Exclude<'key1' | 'key2', 'key2'>` // 等價于 type A = `Exclude<'key1', 'key2'>` | `Exclude<'key2', 'key2'>` // => type A = ('key1' extends 'key2' ? never : 'key1') | ('key'2 extends 'key2' ? never : 'key2') // => // never是所有類型的子類型 type A = 'key1' | never = 'key1'
- Extract
高級類型Extract
和上面的Exclude
剛好相反,它是將第二個參數(shù)的聯(lián)合項從第一個參數(shù)的聯(lián)合項中提取出來
,當(dāng)然,第二個參數(shù)可以含有第一個參數(shù)沒有的項。
下面是其定義和一個例子,有興趣可以自己推導(dǎo)一下
type Extract<T, U> = T extends U ? T : never type A = Extract<'key1' | 'key2', 'key1'> // 'key1'
- Pick
extends
的條件判斷,除了定義條件類型,還能在泛型表達(dá)式中用來約束泛型參數(shù)
// 高級類型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'> // 報錯:類型“"key" | "noSuchKey"”不滿足約束“keyof A” type A2 = Pick<A, 'name'|'noSuchKey'>
Pick
的意思是,從接口T中,將聯(lián)合類型K中涉及到的項挑選出來,形成一個新的接口,其中K extends keyof T
則是用來約束K的條件,即,傳入K的參數(shù)必須使得這個條件為真,否則ts就會報錯,也就是說,K的聯(lián)合項必須來自接口T的屬性。
以上就是ts中 extends
關(guān)鍵字的常用場景。
參考文獻(xiàn)
https://www.typescriptlang.org/docs/handbook/2/classes.html#extends-clauses
https://www.typescriptlang.org/docs/handbook/2/objects.html#extending-types
https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints
https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types
總結(jié)
到此這篇關(guān)于Typescript中extends關(guān)鍵字基本使用的文章就介紹到這了,更多相關(guān)ts中extends關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實現(xiàn)前端飛機(jī)大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實現(xiàn)前端飛機(jī)大戰(zhàn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05js中toString()和String()區(qū)別詳解
本文主要介紹了js中toSring()和Sring()的區(qū)別。具有很好的參考價值。下面跟著小編一起來看下吧2017-03-03微信小程序天氣預(yù)報功能實現(xiàn)(支持自動定位,附源碼)
對于一個經(jīng)常出門在外的人,關(guān)注天氣是至關(guān)重要的,下面這篇文章主要給大家介紹了關(guān)于微信小程序天氣預(yù)報功能實現(xiàn)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),支持自動定位,需要的朋友可以參考下2022-04-04