<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊 Typescript 中的 extends 關(guān)鍵字

          共 5815字,需瀏覽 12分鐘

           ·

          2021-09-20 04:12

          前言

          extends關(guān)鍵字在TS編程中出現(xiàn)的頻率挺高的,而且不同場(chǎng)景下代表的含義不一樣,特此總結(jié)一下:

          • 表示繼承/拓展的含義
          • 表示約束的含義
          • 表示分配的含義

          基本使用

          extends是 ts 里一個(gè)很常見的關(guān)鍵字,同時(shí)也是 es6 里引入的一個(gè)新的關(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 方法,因?yàn)榭梢栽?Dog 實(shí)例 dog 上調(diào)用。

          • 繼承某個(gè)類型

          在 ts 里,extends除了可以像 js 繼承值,還可以繼承/擴(kuò)展類型:

          ?interface?Animal?{
          ???kind:?string;
          ?}

          ?interface?Dog?extends?Animal?{
          ???bark():?void;
          ?}
          ?//?Dog?=>?{?name:?string;?bark():?void?}

          泛型約束

          在書寫泛型的時(shí)候,我們往往需要對(duì)類型參數(shù)作一定的限制,比如希望傳入的參數(shù)都有 name 屬性的數(shù)組我們可以這么寫:

          function?getCnames<T?extends?{?name:?string?}>(entities:?T[]):string[]?{
          ??return?entities.map(entity?=>?entity.cname)
          }

          這里extends對(duì)傳入的參數(shù)作了一個(gè)限制,就是 entities 的每一項(xiàng)可以是一個(gè)對(duì)象,但是必須含有類型為stringcname屬性。再比如,redux 里 dispatch 一個(gè) action,必須包含?type屬性:

          interface?Dispatch<T?extends?{?type:?string?}>?{
          ??(action:?T):?T
          }

          條件類型與高階類型

          SomeType?extends?OtherType???TrueType?:?FalseType;

          When the type on the left of the extendsis assignable 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還有一大用途就是用來判斷一個(gè)類型是不是可以分配給另一個(gè)類型,這在寫高級(jí)類型的時(shí)候非常有用,舉個(gè) ??:

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

          在 vscode 里或者 ts playground 里輸入這段代碼,你會(huì)發(fā)現(xiàn) Bool 的類型是'yes'。這是因?yàn)?Human 和 Duck 的類型完全相同,或者說 Human 類型的一切約束條件,Duck 都具備;換言之,類型為 Human 的值可以分配給類型為 Duck 的值(分配成功的前提是,Duck里面得的類型得有一樣的),反之亦然。需要理解的是,這里A extends B,是指類型A可以分配給類型B,而不是說類型A是類型B的子集。稍微擴(kuò)展下來詳細(xì)說明這個(gè)問題:

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

          當(dāng)我們給Human加上一個(gè)occupation屬性,發(fā)現(xiàn)此時(shí)Bool'no',這是因?yàn)?Duck 沒有類型為stringoccupation屬性,類型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

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

          When conditional types act on a generic type, they become?distributive?when given a union type

          這句話翻譯過來也還是看不懂,我直接上大白話了

          對(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é)果。

          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是一個(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?P<T>?=?[T]?extends?['x']???string?:?number;
          ??type?A1?=?P<'x'?|?'y'>?//?number
          ??type?A2?=?P<never>?//?string

          在條件判斷類型的定義中,將泛型參數(shù)使用[]括起來,即可阻斷條件判斷類型的分配,此時(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ù)。

          示例:

          type?A?=?Exclude<'key1'?|?'key2',?'key2'>?//?'key1'

          Exclude的定義是

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

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

          type?A?=?`Exclude<'key1'?|?'key2',?'key2'>`

          //?等價(jià)于

          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

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

          下面是其定義和一個(gè)例子,有興趣可以自己推導(dǎo)一下

          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的屬性。

          以上就是ts中?extends?關(guān)鍵字的常用場(chǎng)景。

          參考文獻(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


          往期推薦

          6 分鐘了解 HTTP 發(fā)展史

          分享小冊(cè)《Chrome-DevTools的使用技巧》中實(shí)用的內(nèi)容

          從 curl 命令行視角來講解HTTP請(qǐng)求

          深入對(duì)比 eslint 插件 和 babel 插件的異同點(diǎn)

          你不知道 CSS 可以做的 4 件事

          六個(gè)問題讓你更懂 React Fiber,了解框架底層渲染邏輯

          點(diǎn)擊下方“技術(shù)漫談”,選擇“設(shè)為星標(biāo)
          第一時(shí)間關(guān)注技術(shù)干貨!
          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋中文字幕一区 | 操操片| 亚洲中文字幕不卡在线 | 色婷婷一区二区三区久久午夜成人 | 孕妇XXX另类孕交 |