<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 類型系統(tǒng):分布式條件類型全解

          共 6689字,需瀏覽 14分鐘

           ·

          2022-05-20 02:36

          本文是對在極客時間 與 早早聊 的直播 中提到的 分布式條件類型 的進(jìn)一步說明,但你不需要已經(jīng)觀看過相關(guān)直播,本文會包括前置知識部分。

          注意:本文的重點是分布式條件類型,對于前置的條件類型部分不會有特別深入的講解,但其實也夠了。

          本文的主要內(nèi)容仍然主要來自于對之前的 TypeScript的另一面:類型編程(2021重制版) 一文中,對分布式條件類型的講解,也推薦仍處于學(xué)習(xí)階段的同學(xué)完整的閱讀這篇文章,我敢保證看完你的 TypeScript 水平就已經(jīng)超越 79.8% 的使用者了。

          條件類型

          在本文中,我們只會介紹 extends 語句在常見情況下的成立情況,其他與條件類型相關(guān)的知識如原理、infer 關(guān)鍵字、基于條件類型的泛型約束等不做介紹(因為和本文主旨并冇關(guān)系)。

          常見的 extends 語句可以分成這么幾種成立情況:

          • 字面量類型及其原始類型
          • 基類與派生類的子類型關(guān)系
          • 基于結(jié)構(gòu)化類型系統(tǒng)的子類型關(guān)系
          • 聯(lián)合類型與其分支的子類型關(guān)系
          • Top Type 與 Bottom Type
          • 分布式條件類型

          我們會一一介紹。

          字面量類型及其原始類型

          字面量類型包括數(shù)字字面量、字符串字面量以及布爾字符串類型,它們是比原始類型更有意義的類型信息,如我們可以標(biāo)注一個可選值確定的返回值:

          一般認(rèn)為模板字符串類型近似于字面量類型。

          type?ResCode?=?200?|?400?|?500;

          我們可以標(biāo)注一個確定的字符串集合:

          interface?Foo?{
          ??status:?'success'?|?'failure'
          }

          也可以混用:

          type?Mixed?=?true?|?599?|?'Linbudu'

          本質(zhì)上,對于字面量類型,其可以理解為它是對原始類型的進(jìn)一步收斂,是比原始類型更精確的類型,因此對于 extends 語句其必然成立。

          //?true
          type?_T1?=?'linbudu'?extends?string???true?:?false;

          從另一種角度來說,我們也可以認(rèn)為字面量類型其實就是繼承自原始類型,如以下偽代碼:

          class?LinbuduLiteralType?extends?String?{
          ??public?value?=?'Linbudu';
          }

          子類型關(guān)系

          對于基類和派生類的子類型關(guān)系,這一部分不做解釋,直接上代碼:

          class?Base?{
          ??name!:?string;
          }

          class?Derived?extends?Base?{
          ??age!:?number;
          }

          //?true
          type?_T1?=?Derived?extends?Base???true?:?false;

          還存在著一種類似的情況,即結(jié)構(gòu)化類型系統(tǒng)判斷得到的子類型關(guān)系

          關(guān)于結(jié)構(gòu)化類型系統(tǒng)和標(biāo)稱類型系統(tǒng)的差異,咱們以后再說。

          type?_T2?=?{?name:?'linbudu';?}?extends?Base
          ????true
          ??:?false;

          type?_T3?=?{?name:?'linbudu';?age:?18;?job:?'engineer'?}?extends?Base
          ????true
          ??:?false;

          在這里我們手動定義的對象并沒有真的 extends Base Class,但由于其內(nèi)部的屬性與 Base 類型一致(_T3 ?還額外進(jìn)行了擴展),結(jié)構(gòu)化類型系統(tǒng)通過比較內(nèi)部的屬性與屬性類型來判斷類型的兼容性,因此這里 extends 同樣成立。

          還有一種更特殊的情況,即對于空對象{} 的比較:

          type?_T4?=?{}?extends?{}
          ????true
          ??:?false;

          type?_T5?=?{?name:?'linbudu';??}?extends?{}
          ????true
          ??:?false;
          ??
          type?_T6?=?string?extends?{}
          ????true
          ??:?false;

          本質(zhì)上類似于基類以及派生類,但空對象由于其內(nèi)部無屬性,任意一個對象(甚至是原始類型)都可以認(rèn)為是它的子集。

          聯(lián)合類型及其分支

          對于聯(lián)合類型的比較,其實就是比較前者的類型分支在后者中是否都存在,或者說前者是否是后者的子集

          //?true
          type?_T7?=?'a'?extends?'a'?|?'b'?|?'c'???true?:?false;

          //?true
          type?_T8?=?'a'?|?'b'?extends?'a'?|?'b'?|?'c'???true?:?false;

          //?false
          type?_T9?=?'a'?|?'b'?|?'wuhu!'?extends?'a'?|?'b'?|?'c'???true?:?false;

          在分布式條件類型一節(jié)中,我們會了解更多。

          Top Type 與 Bottom Type

          是的,這又是一個獨立的知識點,我們還是以后... 所以你知道 TypeScript 類型系統(tǒng)的知識有多重要了吧。

          在 TypeScript 我們說 any 與 unknown 是 Top Type,而 never 則是 Bottom Type。Top Type 意味著它們處于類型層級的頂端,即任意的類型都屬于其子類型,對于 OtherType extends anyOtherType extends unknown 必定成立。而 Bottom Type 處于類型層級的底端,意味著無法再細(xì)分的類型,除了 never 自身,沒有別的類型能夠再賦值給它。這也就意味著,它屬于任意類型的子類型,即 never extends OtherType 必定成立。

          那么,一環(huán)一環(huán)的套起來,我們就可以構(gòu)造出一條 extends 鏈,來直觀地分析 TypeScript 的類型層級了:

          //?8,即所有?extends?均成立
          type?_Chain?=?never?extends?'linbudu'
          ????'linbudu'?extends?'linbudu'?|?'budulin'
          ??????'linbudu'?extends?string
          ????????string?extends?{}
          ??????????{}?extends?Object
          ????????????Object?extends?any
          ??????????????Object?extends?unknown
          ????????????????any?extends?unknown
          ??????????????????unknown?extends?any
          ????????????????????8
          ??????????????????:?7
          ????????????????:?6
          ??????????????:?5
          ????????????:?4
          ??????????:?3
          ????????:?2
          ??????:?1
          ????:?0
          ??:?never;

          分布式條件類型

          分布式條件類型(Distributive Conditional Types)是 TypeScript 中條件類型的特殊功能之一,因此也被稱為條件類型的分布式特性。

          對于它其實沒有什么特別晦澀難懂的彎彎繞繞,就是滿足一定條件后必然會發(fā)生的事情罷了,就好像餓了要吃飯,困了要睡覺一樣。所以沒必要也不應(yīng)該敬畏它,看我怎么把它扒光在你們面前。

          來看一個例子,對于內(nèi)置工具類型 Exclude 的使用:

          type?Extract?=?T?extends?U???T?:?never;
          interface?IObject?{
          ??a:?string;
          ??b:?number;
          ??c:?boolean;
          }

          //?'a'|'b'
          type?_ExtractedKeys1?=?Extract'a'|'b'>;

          //?'a'|'b'
          type?_ExtractedKeys2?=?Extract<'a'|'b'|'c',?'a'|'b'>;

          //?never
          type?_ExtractedKeys3?=?'a'|'b'|'c'?extends?'a'|'b'???'a'|'b'|'c'?:?never;

          本質(zhì)上,這三個類型別名執(zhí)行的代碼是一模一樣的,但是為什么 3 和 1、2 表現(xiàn)得不一致(雖然這個 extends 不成立我們是理解的)?

          研究下兩種情況的區(qū)別,你會發(fā)現(xiàn) 1、2 是通過傳入給泛型參數(shù),再由泛型參數(shù)進(jìn)行判斷的,而 3 則是直接使用聯(lián)合類型進(jìn)行的判斷。

          記住第一個差異:是否作為泛型參數(shù)

          再看一個例子:

          type?Naked?=?T?extends?boolean???"Y"?:?"N";
          type?Wrapped?=?[T]?extends?[boolean]???"Y"?:?"N";

          //?"N"?|?"Y"
          type?Result1?=?Naked<number?|?boolean>;

          //?"N"
          type?Result2?=?Wrapped<number?|?boolean>;

          這里倒是都通過泛型參數(shù)進(jìn)行判斷了,但結(jié)果又不一樣了,為什么第一個得到了聯(lián)合類型?第二個倒是能理解,[1, true] 肯定和 [true, true] 不兼容嘛。

          對于第一個,你可能已經(jīng)發(fā)現(xiàn)這里作為結(jié)果的聯(lián)合類型,恰好對應(yīng)上了分別使用 number、boolean 判斷結(jié)果,就好像于謙的父親也姓于一樣這么巧?那為什么第二個沒有被這樣子拆開比較?

          記住第二個差異:泛型參數(shù)在條件類型是否被數(shù)組包裹了

          其實這里的兩個差異就是分布式條件類型要發(fā)生的前提條件:

          • 首先,你得是聯(lián)合類型
          • 其次,你的聯(lián)合類型需要是通過泛型參數(shù)的形式傳入
          • 最后,你的泛型參數(shù)在條件類型語句中需要是裸類型參數(shù),即沒有被 [] 包裹

          合起來,我們就得到了官方的解釋:對于屬于裸類型參數(shù)的檢查類型,條件類型會在實例化時期自動分發(fā)到聯(lián)合類型上。

          Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation.

          現(xiàn)在我們可以講講這里的分布式代表啥了,第一個例子中實際上是這么一個判斷過程:

          //?('a'?extends?'a'|'b')?|?('b'?extends?'a'|'b')?|?('c'?extends?'a'|'b')
          //?'a'|'b'|never
          //?'a'|'b'
          type?_ExtractedKeys1?=?Extract'a'|'b'>;

          即聯(lián)合類型的分支被單獨的拿出來依次進(jìn)行條件類型語句的判斷。類似的,第二個例子在 Naked 工具類型也會將傳入的聯(lián)合類型參數(shù)進(jìn)行分發(fā)判斷,而 Wrapped 由于不滿足裸類型參數(shù)的條件,導(dǎo)致分布式不成立,因此不會進(jìn)行分發(fā)。

          你可能會好奇,為啥要專門設(shè)計分布式條件類型這個東西?實際上,可以說它也是 TypeScript 類型系統(tǒng)的基石之一,大量的工具類型底層都依賴了它來進(jìn)行聯(lián)合類型的過濾,然后基于聯(lián)合類型的過濾去做映射類型、Pick/Omit 這一類接口裁剪操作,準(zhǔn)備開始實戰(zhàn)環(huán)節(jié)!

          分布式條件類型的應(yīng)用

          TypeScript 內(nèi)置了幾個基于分布式條件類型的工具類型:

          type?Extract?=?T?extends?U???T?:?never;

          type?Exclude?=?T?extends?U???never?:?T;

          type?NonNullable?=?T?extends?null?|?undefined???never?:?T;

          它們的作用分別是:

          • Extract,提取 集合T 中也存在于 集合U 中的類型分支,即 T 與 U 的交集
          • Exclude,提取 集合T 中不存在于 集合U 中的類型分支,即 T 相對于 U 的差集
          • NonNullable,移除集合中的 null 與 undefined

          它們的工作機理就是分布式條件類型了,看到了差集與交集,你的數(shù)學(xué) DNA 是否動了?

          當(dāng)然,我們可以很容易得實現(xiàn)并集與補集:

          export?type?Concurrence?=?A?|?B;

          export?type?Complementextends?A>?=?Exclude;

          勉為其難畫個圖好了,我可不是隨便畫圖的人。

          并集、補集、差集、交集

          唯一需要注意的就是補集,補集實際上是特殊情況的差集,即 U 為 T 的子集,此時有 T 相對于 U 的差集 + U = T

          這里我們實現(xiàn)了普通集合的情況,如果眉頭一皺,你會發(fā)現(xiàn),問題并不簡單。如果我們面臨的是對象的情況呢?這個時候如果我們?nèi)ο蟛⒓蔷蜎]那么簡單了,比如,如果一個鍵在兩個對象中都存在,那么以哪個對象的鍵值類型為準(zhǔn)?又比如,如果我們想實現(xiàn)合并對象時,只使用新對象的鍵值類型覆蓋掉原對象的同鍵鍵值類型,但不想合并新的鍵過來?

          這其實就是類型編程中,我稱之為一刀兩端藕斷絲連再續(xù)前緣重歸于好的操作,其實就是把一個接口,拆成多個部分,對某一部分做處理,然后再合并。這一思想在許多工具類型中都有體現(xiàn),如:

          export?type?MarkPropsAsOptional<
          ??T?extends?Record<string,?any>,
          ??K?extends?keyof?T?=?keyof?T
          >?=?Omit?&?Partial>;

          這個工具類型的作用是將接口中指定的一部分變?yōu)榭蛇x,而不像 Partial 那樣全量的變?yōu)榭蛇x。它的思路就是把接口拆成 保持不變的 + 需要標(biāo)記為可選的,然后對后一部分應(yīng)用 Partial 類型,再合并即可。

          回到對象的覆蓋,其實思路是一樣的。

          • 合并 T 和 U 的所有鍵值對,以 U 的鍵值類型優(yōu)先級更高
            • T 比 U 多的部分:T 相對于 U 的差集
            • U 比 T 多的部分:U 相對于 T 的差集
            • T 與 U 的交集,通過 Extract,將 T 視為后入集合,實現(xiàn)以 U 為主集合,即 U 的類型優(yōu)先級更高。

          在開始前,我們還需要來實現(xiàn)對象交集、對象差集等幾個輔助工具類型,以及輔助它們的對象鍵集合交集、對象鍵集合差集的輔助輔助工具類型(笑。為了方便理解,我們把 Extract 命名為 Intersection, Exclude 命名為 Difference。

          type?PlainObjectType?=?Record<string,?any>;

          export?type?Intersection?=?A?extends?B???A?:?never;

          export?type?Difference?=?A?extends?B???never?:?A;

          export?type?ObjectKeysIntersection<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          >?=?Intersection?&?Intersection;

          export?type?ObjectKeysDifference<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          >?=?Difference;

          export?type?ObjectIntersection<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          >?=?Pick>;

          export?type?ObjectDifference<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          >?=?Pick>;

          這樣就可以實現(xiàn)以新對象類型優(yōu)先級更高的 Merge 了:

          type?Merge<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          ??//?T?比?U?多的部分,加上?T?與?U?交集的部分(類型不同則以?U?優(yōu)先級更高,再加上?U?比?T?多的部分即可
          >?=?ObjectDifference?&?ObjectIntersection?&?ObjectDifference;

          類似的,如果要保證原對象類型優(yōu)先級更高,反轉(zhuǎn)下交集即可:

          type?Assign<
          ??T?extends?PlainObjectType,
          ??U?extends?PlainObjectType
          ??//?T?比?U?多的部分,加上?T?與?U?交集的部分(類型不同則以?T?優(yōu)先級更高,再加上?U?比?T?多的部分即可
          >?=?ObjectDifference?&?ObjectIntersection?&?ObjectDifference;

          擴展

          & 與 |

          可能前面有的同學(xué)注意到了,我們的普通集合交集使用的是 | ,但對象的交集使用的是交叉類型 &:

          export?type?Concurrence?=?A?|?B;

          //?此?IntersectionTypes?非彼?Intersection
          //?懶得寫約束了,反正都得是對象類型就是了,西內(nèi)!
          export?type?IntersectionTypes?=?T?&?U?&?K;

          這是因為,只有對于對象類型,& 才表現(xiàn)為合并的情況,而對于原始類型以及聯(lián)合類型,& 就真的表現(xiàn)為,交集。

          //?'a'
          type?_T1?=?('a'?|?'b')?&?('a'?|?'d'?|?'e'?|?'f')

          //?never,因為?string?和?number?哪有交集啊
          type?_T1?=?string?&?number;

          家庭作業(yè)

          小明想要把兩個對象 A、B 合并在一起,但不想要對象 B 中比對象 A 多的部分,而是只想把 B 中同鍵的鍵值類型合并過來,你能幫幫他嗎?

          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  波多野结衣在线成人视频 | 亚洲7777 | 久久视频这里有精品 | 天天操夜 | 色播婷婷丁香五月 |