<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>

          淺談 TS 標(biāo)稱類型介紹及社區(qū)實(shí)現(xiàn)

          共 5699字,需瀏覽 12分鐘

           ·

          2022-02-22 01:22



          前言



          有位大神說(shuō)過(guò)"程序是類型的證明",我看不懂,但我大受震撼。為了以后能看懂哪怕一點(diǎn)點(diǎn),我決定記錄下類型相關(guān)的所學(xué)所悟。

          《淺談 TS 標(biāo)稱類型》系列將以稍偏門的視角來(lái)看待 TypeScript 的類型系統(tǒng),實(shí)際用途不大,但自覺(jué)有趣。本文是該系列的開篇文章,主要介紹標(biāo)簽類型是什么,以及 TS 社區(qū)都有哪些實(shí)現(xiàn)手段。




          什么是標(biāo)稱類型系統(tǒng)(nominal type system)




          先通俗地理解下,舉個(gè)例子,userId = 123bookId = 34都是數(shù)字,但兩者用于不同的場(chǎng)景,希望用不同類型 UserIDBookID 來(lái)表示,且不能互換。像這樣,數(shù)據(jù)的值本身沒(méi)什么區(qū)別,安上不同名字就是不同類型,這就是標(biāo)稱類型系統(tǒng)(nominal type system)。也就是說(shuō),標(biāo)稱類型系統(tǒng)中,兩個(gè)變量是否類型兼容(可以交換賦值)取決于這兩個(gè)變量顯式聲明的類型名字是否相同。

          與之相對(duì)的是結(jié)構(gòu)類型系統(tǒng)(structural type system),類型兼容只取決于實(shí)際結(jié)構(gòu)是否相同,與類型名字無(wú)關(guān)。比如:定義Point類型包含x、y兩個(gè)數(shù)字,rect = { x: 33, y: 3, width: 30, height: 80 }的結(jié)構(gòu)滿足Point的定義,就屬于Point類型。簡(jiǎn)單理解,結(jié)構(gòu)類型系統(tǒng)中,結(jié)構(gòu)或者說(shuō)形狀相同的兩個(gè)值,它們的類型是兼容的,可以交換賦值。

          更嚴(yán)格的定義可以看下Type system - Wikipedia的說(shuō)明。

          除了上面的 UserIDBookID 的例子,標(biāo)稱類型還有其他常見(jiàn)的應(yīng)用場(chǎng)景,比如:區(qū)分不同的字符串(正則表達(dá)式、html模版、文件路徑等),表達(dá)不同單位的量綱(不同幣種的金額、css各種長(zhǎng)度單位)等。這些會(huì)在后續(xù)文章再展開說(shuō)明,屆時(shí)也會(huì)列舉下標(biāo)稱類型常見(jiàn)的錯(cuò)誤用法。




          TS 是標(biāo)稱類型系統(tǒng)嗎




          不是。TS 是結(jié)構(gòu)類型系統(tǒng)(structural type system),基于結(jié)構(gòu)/形狀檢查類型,而非類型的名字。

          One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.

          TypeScript: Documentation - TypeScript for JavaScript Programmers

          上面是TS官方文檔的說(shuō)明,里面還舉了一些例子,可以先看看加深理解。




          TS 可以實(shí)現(xiàn)標(biāo)稱類型嗎




          可以(不然這篇文章寫到這里就要結(jié)束了??)。TS 目前不支持顯式聲明標(biāo)稱類型,也沒(méi)有計(jì)劃支持,2014年的提案Support some non-structural (nominal) type matching · Issue #202 到現(xiàn)在還是Open狀態(tài)。不過(guò)社區(qū)有不少方案,可以基于現(xiàn)有 TS 的能力一定程度上實(shí)現(xiàn)標(biāo)稱類型,整理如下。



          TS 實(shí)現(xiàn)標(biāo)稱類型的各種手段




          為了方便,下面都用 CNY、USD 幣種來(lái)示例,類型檢查用下面兩個(gè)方法測(cè)試。

          function?buyPekingDuck(money:?CNY)?{}?//?只能用?CNY?買北京烤鴨
          function?buyCocaCola(money:?USD)?{}?//?只能用?USD?買可口可樂(lè)

          為了術(shù)語(yǔ)一致,下文統(tǒng)一用下列中文字詞(若與習(xí)慣的表述不一致,請(qǐng)以英文單詞為準(zhǔn))

          • Type Annotation: 類型聲明 ?變量: 類型 ,比如 let yuan: CNY
          • Type Assertion: 類型斷言 表達(dá)式 as 類型,比如 12 as CNY
          • Type Compatibility: 類型兼容,指一個(gè)類型可以賦值給另一個(gè)類型
          • Type Infer: 類型推斷,指 TS 根據(jù)上下文推斷變量或值的類型,比如 let a = 12 推斷 anumber
          • Primitive Type: 原始類型,指string, numberboolean

          1、定義私有屬性的類 Class with a private property

          class?CNY?{
          ??private?__brand:?void
          ??constructor(public?value:?number)?{}
          }
          class?USD?{
          ??private?__brand:?void
          ??constructor(public?value:?number)?{}
          }

          //?用例
          const?yuan?=?new?CNY(12)
          const?dollar?=?new?USD(5)

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USD'?is?not?assignable?to?parameter?of?type?'CNY'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNY'?is?not?assignable?to?parameter?of?type?'USD'.

          這個(gè)方法利用了 TS 對(duì) private/protected 的特殊處理——判斷類型兼容時(shí),如果其中一個(gè)包含私有屬性,則另一個(gè)必須包含來(lái)自同一個(gè)類聲明的相同私有屬性。yuandollar 都有私有屬性__brand,但來(lái)自不同的類聲明(分別是CNYUSD),所以它們類型不兼容。

          優(yōu)點(diǎn):不需要類型聲明(type annotation),也不需要類型斷言(type assertion),TS 能推導(dǎo)出對(duì)應(yīng)的類型(type infer)。缺點(diǎn):冗余的類聲明,多了一層{ value }的結(jié)構(gòu),不能支持原始類型,需要額外的序列化處理。推薦度:不推薦。除非本來(lái)就是用類實(shí)現(xiàn),而且要嚴(yán)格區(qū)分字段相同、語(yǔ)義不同的兩個(gè)類型,才考慮該方案。

          2、包含字面量類型

          type?CNY?=?{
          ??currency:?'CNY',
          ??value:?number,
          }
          type?USD?=?{
          ??currency:?'USD',
          ??value:?number,
          }

          //?用例
          const?yuan:?CNY?=?{?currency:?'CNY',?value:?12?}
          const?dollar:?USD?=?{?currency:?'USD',?value:?5}

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USD'?is?not?assignable?to?parameter?of?type?'CNY'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNY'?is?not?assignable?to?parameter?of?type?'USD'.

          加入不同的字面量類型(literal type)來(lái)定義 type 或 interface,因?yàn)椴煌置媪渴遣煌愋?,所以組合后的類型也不同。

          優(yōu)點(diǎn):語(yǔ)義清晰,理解直觀,條件判斷能實(shí)現(xiàn)類型收窄(type narrowing)。缺點(diǎn):多了一層{ value }的結(jié)構(gòu),不能支持原始類型,需要額外的序列化處理。推薦度:看情況。如果本來(lái)有結(jié)構(gòu),而且用于區(qū)分的字面量有對(duì)應(yīng)的語(yǔ)義,可以用該方法。

          3、枚舉類 intersection

          enum?CNYBrand?{?_brand?}
          type?CNY?=?number?&?CNYBrand

          enum?USDBrand?{?_brand?}
          type?USD?=?number?&?USDBrand

          //?用例
          const?yuan?=?12?as?CNY
          const?dollar?=?5?as?USD

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USDBrand'?is?not?assignable?to?parameter?of?type?'CNYBrand'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNYBrand'?is?not?assignable?to?parameter?of?type?'USDBrand'.

          枚舉定義了{ _brand },TS會(huì)認(rèn)為是非空數(shù)字枚舉,兩個(gè)枚舉不兼容,與數(shù)字類型交集后就是不同類型。

          注意,字符串不能這么用,string & CNYBrand的結(jié)果是never。枚舉需要定義為{ _brand: ''},讓TS認(rèn)為是非空字符串枚舉,才能跟字符串類型取交集。

          優(yōu)點(diǎn):無(wú),勉強(qiáng)要說(shuō)的話,類型斷言的 as Xxx 可讀性還行。缺點(diǎn):需要類型斷言,有額外的枚舉定義,會(huì)生成多余的js代碼,數(shù)字和字符串類型用法不一樣,不支持其他原始類型(布爾類型)。推薦度:不推薦。為了標(biāo)稱類型增加額外運(yùn)行損耗,不值得。

          4、unique symbol

          type?CNY?=?number?&?{
          ??readonly?brand:?unique?symbol
          }

          type?USD?=?number?&?{
          ??readonly?brand:?unique?symbol
          }

          //?用例
          const?yuan?=?12?as?CNY
          const?dollar?=?5?as?USD

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USD'?is?not?assignable?to?parameter?of?type?'CNY'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNY'?is?not?assignable?to?parameter?of?type?'USD'.

          TS 里每個(gè) unique symbol 聲明都是完全獨(dú)立的唯一標(biāo)識(shí),互相不兼容。作為屬性加到類型中需要用readonly修飾。

          優(yōu)點(diǎn):類型定義部分無(wú)差異,不用費(fèi)心思,無(wú)額外的結(jié)構(gòu),運(yùn)行時(shí)無(wú)消耗。缺點(diǎn):需要類型斷言,關(guān)鍵字較多(uniquereadonly),不能用范型。推薦度:推薦。不會(huì)生成額外代碼,其唯一性確保類型不會(huì)重復(fù)。

          5、brand interface

          interface?CNY?extends?Number?{
          ??_CNYBrand:?string;
          }
          interface?USD?extends?Number?{
          ??_USDBrand:?string;
          }

          //?用例
          const?yuan:?CNY?=?12?as?any
          const?dollar:?USD?=?5?as?any

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USD'?is?not?assignable?to?parameter?of?type?'CNY'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNY'?is?not?assignable?to?parameter?of?type?'USD'.

          用 interface 擴(kuò)展增加互不相同的_xxxBrand變成不同的類型,破壞類型兼容。TS 的源碼也使用了該方案。

          優(yōu)點(diǎn):支持基本類型,沒(méi)用到黑魔法,無(wú)額外的結(jié)構(gòu),運(yùn)行時(shí)無(wú)消耗。缺點(diǎn):需要類型聲明或類型斷言,且需要過(guò) any 一道。推薦度:非常推薦。大部分需要標(biāo)稱類型的場(chǎng)景不會(huì)直接指定類型,缺點(diǎn)可接受,優(yōu)先考慮該方案。

          6、brand type intersection

          type?CNY?=?number?&?{
          ??_CNYBrand:?string;
          }
          type?USD?=?number?&?{
          ??_USDBrand:?string;
          }

          //?用例
          const?yuan:?CNY?=?12?as?any
          const?dollar:?USD?=?5?as?any

          //?類型安全
          buyPekingDuck(dollar)?//?Argument?of?type?'USD'?is?not?assignable?to?parameter?of?type?'CNY'.
          buyCocaCola(yuan)?//?Argument?of?type?'CNY'?is?not?assignable?to?parameter?of?type?'USD'.

          同上,只不過(guò) interface extend 改成等價(jià)的 type intersection,即,用類型交集增加互不相同的_xxxBrand變成不同的類型,破壞類型兼容。

          優(yōu)點(diǎn):支持基本類型,沒(méi)用到黑魔法,無(wú)額外的結(jié)構(gòu),運(yùn)行時(shí)無(wú)消耗。缺點(diǎn):需要類型聲明或類型斷言,且需要過(guò) any 一道。推薦度:非常推薦。同上,大部分需要標(biāo)稱類型的場(chǎng)景不會(huì)直接指定類型,缺點(diǎn)可接受,優(yōu)先考慮該方案。


          上面列舉了社區(qū)常見(jiàn)的標(biāo)稱類型實(shí)現(xiàn)方法,其中個(gè)人最推薦的是 brand interface 以及等價(jià)的 brand type intersection,原理簡(jiǎn)單易懂,沒(méi)有黑魔法,適合絕大多數(shù)使用場(chǎng)景,也是 TS 官方源碼里在用的方法,值得優(yōu)先考慮。




          后記




          本文簡(jiǎn)單介紹了標(biāo)稱類型是什么,以及 TS 中如何實(shí)現(xiàn)。除了本文提到的這些方法外,網(wǎng)上還能找到很多標(biāo)稱類型的實(shí)現(xiàn)手段,它們各有優(yōu)劣,適用場(chǎng)景也有差異,而且隨著 TS 升級(jí),有些方法已經(jīng)失效了,不熟悉的話可能會(huì)難以抉擇,故沒(méi)有收錄到文章中。

          本系列后續(xù)文章會(huì)從實(shí)現(xiàn)原理進(jìn)一步剖析這些方法,了解其背后的機(jī)制,并結(jié)合實(shí)際使用場(chǎng)景來(lái)辨析,爭(zhēng)取知其然,知其所以然。


          瀏覽 131
          點(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>
                  囯产精品久久久 | 欧美成人三级在线视频 | 无码一卡二卡 | av在线免费网站 a片黄色成人电影 | 国产高清成人无码视频网址 |