?2?*?num//?tsconst?double?=?(num:?number..." />
<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 高級(jí)類型

          共 6750字,需瀏覽 14分鐘

           ·

          2020-09-16 22:06

          前言

          對(duì)于有 JavaScript 基礎(chǔ)的同學(xué)來(lái)說(shuō),入門(mén) TypeScript 其實(shí)很容易,只需要簡(jiǎn)單掌握其基礎(chǔ)的類型系統(tǒng)就可以逐步將 JS 應(yīng)用過(guò)渡到 TS 應(yīng)用。

          //?js
          const?double?=?(num)?=>?2?*?num

          //?ts
          const?double?=?(num:?number):?number?=>?2?*?num

          然而,當(dāng)應(yīng)用越來(lái)越復(fù)雜,我們很容易把一些變量設(shè)置為 any 類型,TypeScript 寫(xiě)著寫(xiě)著也就成了 AnyScript。為了讓大家能更加深入的了解 TypeScript 的類型系統(tǒng),本文將重點(diǎn)介紹其高級(jí)類型,幫助大家擺脫 AnyScript。

          泛型

          在講解高級(jí)類型之前,我們需要先簡(jiǎn)單理解泛型是什么。

          泛型是強(qiáng)類型語(yǔ)言中比較重要的一個(gè)概念,合理的使用泛型可以提升代碼的可復(fù)用性,讓系統(tǒng)更加靈活。下面是維基百科對(duì)泛型的描述:

          泛型允許程序員在強(qiáng)類型程序設(shè)計(jì)語(yǔ)言中編寫(xiě)代碼時(shí)使用一些以后才指定的類型,在實(shí)例化時(shí)作為參數(shù)指明這些類型。

          泛型通過(guò)一對(duì)尖括號(hào)來(lái)表示(<>),尖括號(hào)內(nèi)的字符被稱為類型變量,這個(gè)變量用來(lái)表示類型。

          function?copy<T>(arg:?T):?T?{
          ??if?(typeof?arg?===?'object')?{
          ????return?JSON.parse(
          ??????JSON.stringify(arg)
          ????)
          ??}?else?{
          ????return?arg
          ??}
          }

          這個(gè)類型 T,在沒(méi)有調(diào)用 copy 函數(shù)的時(shí)候并不確定,只有調(diào)用 copy 的時(shí)候,我們才知道 T 具體代表什么類型。

          const?str?=?copy<string>('my?name?is?typescript')
          類型

          我們?cè)?VS Code 中可以看到 copy 函數(shù)的參數(shù)以及返回值已經(jīng)有了類型,也就是說(shuō)我們調(diào)用 copy 函數(shù)的時(shí)候,給類型變量 T 賦值了 string。其實(shí),我們?cè)谡{(diào)用 copy 的時(shí)候可以省略尖括號(hào),通過(guò) TS 的類型推導(dǎo)是可以確定 T 為 string 的。

          類型推導(dǎo)

          高級(jí)類型

          除了 string、number、boolean 這種基礎(chǔ)類型外,我們還應(yīng)該了解一些類型聲明中的一些高級(jí)用法。

          交叉類型(&)

          交叉類型說(shuō)簡(jiǎn)單點(diǎn)就是將多個(gè)類型合并成一個(gè)類型,個(gè)人感覺(jué)叫做「合并類型」更合理一點(diǎn),其語(yǔ)法規(guī)則和邏輯 “與” 的符號(hào)一致。

          T?&?U

          假如,我現(xiàn)在有兩個(gè)類,一個(gè)按鈕,一個(gè)超鏈接,現(xiàn)在我需要一個(gè)帶有超鏈接的按鈕,就可以使用交叉類型來(lái)實(shí)現(xiàn)。

          interface?Button?{
          ??type:?string
          ??text:?string
          }

          interface?Link?{
          ??alt:?string
          ??href:?string
          }

          const?linkBtn:?Button?&?Link?=?{
          ??type:?'danger',
          ??text:?'跳轉(zhuǎn)到百度',
          ??alt:?'跳轉(zhuǎn)到百度',
          ??href:?'http://www.baidu.com'
          }

          聯(lián)合類型(|)

          聯(lián)合類型的語(yǔ)法規(guī)則和邏輯 “或” 的符號(hào)一致,表示其類型為連接的多個(gè)類型中的任意一個(gè)。

          T?|?U

          例如,之前的 Button 組件,我們的 type 屬性只能指定固定的幾種字符串。

          interface?Button?{
          ??type:?'default'?|?'primary'?|?'danger'
          ??text:?string
          }

          const?btn:?Button?=?{
          ??type:?'primary',
          ??text:?'按鈕'
          }

          類型別名(type)

          前面提到的交叉類型與聯(lián)合類型如果有多個(gè)地方需要使用,就需要通過(guò)類型別名的方式,給這兩種類型聲明一個(gè)別名。類型別名與聲明變量的語(yǔ)法類似,只需要把 constlet 換成 type 關(guān)鍵字即可。

          type?Alias?=?T?|?U
          type?InnerType?=?'default'?|?'primary'?|?'danger'

          interface?Button?{
          ??type:?InnerType
          ??text:?string
          }

          interface?Alert?{
          ??type:?ButtonType
          ??text:?string
          }

          類型索引(keyof)

          keyof 類似于 Object.keys ,用于獲取一個(gè)接口中 Key 的聯(lián)合類型。

          interface?Button?{
          ????type:?string
          ????text:?string
          }

          type?ButtonKeys?=?keyof?Button
          //?等效于
          type?ButtonKeys?=?"type"?|?"text"

          還是拿之前的 Button 類來(lái)舉例,Button 的 type 類型來(lái)自于另一個(gè)類 ButtonTypes,按照之前的寫(xiě)法,每次 ButtonTypes 更新都需要修改 Button 類,如果我們使用 keyof 就不會(huì)有這個(gè)煩惱。

          interface?ButtonStyle?{
          ????color:?string
          ????background:?string
          }
          interface?ButtonTypes?{
          ????default:?ButtonStyle
          ????primary:?ButtonStyle
          ????danger:?ButtonStyle
          }
          interface?Button?{
          ????type:?'default'?|?'primary'?|?'danger'
          ????text:?string
          }

          //?使用?keyof?后,ButtonTypes修改后,type?類型會(huì)自動(dòng)修改?
          interface?Button?{
          ????type:?keyof?ButtonTypes
          ????text:?string
          }

          類型約束(extends)

          這里的 extends 關(guān)鍵詞不同于在 class 后使用 extends 的繼承作用,泛型內(nèi)使用的主要作用是對(duì)泛型加以約束。我們用我們前面寫(xiě)過(guò)的 copy 方法再舉個(gè)例子:

          type?BaseType?=?string?|?number?|?boolean

          //?這里表示?copy?的參數(shù)
          //?只能是字符串、數(shù)字、布爾這幾種基礎(chǔ)類型
          function?copy<T?extends?BaseType>(arg:?T):?T?{
          ??return?arg
          }
          copy number

          如果我們傳入一個(gè)對(duì)象就會(huì)有問(wèn)題。

          copy object

          extends 經(jīng)常與 keyof 一起使用,例如我們有一個(gè)方法專門(mén)用來(lái)獲取對(duì)象的值,但是這個(gè)對(duì)象并不確定,我們就可以使用 extendskeyof 進(jìn)行約束。

          function?getValue<T,?K?extends?keyof?T>(obj:?T,?key:?K)?{
          ??return?obj[key]
          }

          const?obj?=?{?a:?1?}
          const?a?=?getValue(obj,?'a')
          獲取對(duì)象的值

          這里的 getValue 方法就能根據(jù)傳入的參數(shù) obj 來(lái)約束 key 的值。

          類型映射(in)

          in 關(guān)鍵詞的作用主要是做類型的映射,遍歷已有接口的 key 或者是遍歷聯(lián)合類型。下面使用內(nèi)置的泛型接口 Readonly 來(lái)舉例。

          type?Readonly?=?{
          ????readonly?[P?in?keyof?T]:?T[P];
          };

          interface?Obj?{
          ??a:?string
          ??b:?string
          }

          type?ReadOnlyObj?=?Readonly
          ReadOnlyObj

          我們可以結(jié)構(gòu)下這個(gè)邏輯,首先 keyof Obj 得到一個(gè)聯(lián)合類型 'a' | 'b'

          interface?Obj?{
          ????a:?string
          ????b:?string
          }

          type?ObjKeys?=?'a'?|?'b'

          type?ReadOnlyObj?=?{
          ????readonly?[P?in?ObjKeys]:?Obj[P];
          }

          然后 P in ObjKeys 相當(dāng)于執(zhí)行了一次 forEach 的邏輯,遍歷 'a' | 'b'

          type?ReadOnlyObj?=?{
          ????readonly?a:?Obj['a'];
          ????readonly?b:?Obj['b'];
          }

          最后就可以得到一個(gè)新的接口。

          interface?ReadOnlyObj?{
          ????readonly?a:?string;
          ????readonly?b:?string;
          }

          條件類型(U ? X : Y)

          條件類型的語(yǔ)法規(guī)則和三元表達(dá)式一致,經(jīng)常用于一些類型不確定的情況。

          T?extends?U???X?:?Y

          上面的意思就是,如果 T 是 U 的子集,就是類型 X,否則為類型 Y。下面使用內(nèi)置的泛型接口 Extract 來(lái)舉例。

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

          如果 T 中的類型在 U 存在,則返回,否則拋棄。假設(shè)我們兩個(gè)類,有三個(gè)公共的屬性,可以通過(guò) Extract 提取這三個(gè)公共屬性。

          interface?Worker?{
          ??name:?string
          ??age:?number
          ??email:?string
          ??salary:?number
          }

          interface?Student?{
          ??name:?string
          ??age:?number
          ??email:?string
          ??grade:?number
          }


          type?CommonKeys?=?Extract
          //?'name'?|?'age'?|?'email'
          CommonKeys

          工具泛型

          TypesScript 中內(nèi)置了很多工具泛型,前面介紹過(guò) ReadonlyExtract 這兩種,內(nèi)置的泛型在 TypeScript 內(nèi)置的 lib.es5.d.ts 中都有定義,所以不需要任何依賴都是可以直接使用的。下面看看一些經(jīng)常使用的工具泛型吧。

          lib.es5.d.ts

          Partial

          type?Partial?=?{
          ????[P?in?keyof?T]?:?T[P]
          }

          Partial 用于將一個(gè)接口的所有屬性設(shè)置為可選狀態(tài),首先通過(guò) keyof T,取出類型變量 T 的所有屬性,然后通過(guò) in 進(jìn)行遍歷,最后在屬性后加上一個(gè) ?

          我們通過(guò) TypeScript 寫(xiě) React 的組件的時(shí)候,如果組件的屬性都有默認(rèn)值的存在,我們就可以通過(guò) Partial 將屬性值都變成可選值。

          import?React?from?'react'

          interface?ButtonProps?{
          ??type:?'button'?|?'submit'?|?'reset'
          ??text:?string
          ??disabled:?boolean
          ??onClick:?()?=>?void
          }

          //?將按鈕組件的?props?的屬性都改為可選
          const?render?=?(props:?Partial?=?{})?=>?{
          ??const?baseProps?=?{
          ????disabled:?false,
          ????type:?'button',
          ????text:?'Hello?World',
          ????onClick:?()?=>?{},
          ??}
          ??const?options?=?{?...baseProps,?...props?}
          ??return?(
          ??????????type={options.type}
          ??????disabled={options.disabled}
          ??????onClick={options.onClick}>
          ??????{options.text}
          ????</button>
          ??)
          }

          Required

          type?Required?=?{
          ????[P?in?keyof?T]-?:?T[P]
          }

          Required 的作用剛好與 ?Partial 相反,就是將接口中所有可選的屬性改為必須的,區(qū)別就是把 Partial 里面的 ? 替換成了 -?

          Record

          type?Recordextends?keyof?any,?T>?=?{
          ????[P?in?K]:?T
          }

          Record 接受兩個(gè)類型變量,Record 生成的類型具有類型 K 中存在的屬性,值為類型 T。這里有一個(gè)比較疑惑的點(diǎn)就是給類型 K 加一個(gè)類型約束,extends keyof any,我們可以先看看 keyof any 是個(gè)什么東西。

          keyof any

          大致一直就是類型 ?K 被約束在 string | number | symbol 中,剛好就是對(duì)象的索引的類型,也就是類型 K 只能指定為這幾種類型。

          我們?cè)跇I(yè)務(wù)代碼中經(jīng)常會(huì)構(gòu)造某個(gè)對(duì)象的數(shù)組,但是數(shù)組不方便索引,所以我們有時(shí)候會(huì)把對(duì)象的某個(gè)字段拿出來(lái)作為索引,然后構(gòu)造一個(gè)新的對(duì)象。假設(shè)有個(gè)商品列表的數(shù)組,要在商品列表中找到商品名為 「每日?qǐng)?jiān)果」的商品,我們一般通過(guò)遍歷數(shù)組的方式來(lái)查找,比較繁瑣,為了方便,我們就會(huì)把這個(gè)數(shù)組改寫(xiě)成對(duì)象。

          interface?Goods?{
          ??id:?string
          ?name:?string
          ??price:?string
          ??image:?string
          }

          const?goodsMap:?Record<string,?Goods>?=?{}
          const?goodsList:?Goods[]?=?await?fetch('server.com/goods/list')

          goodsList.forEach(goods?=>?{
          ??goodsMap[goods.name]?=?goods
          })

          Pick

          type?Pickextends?keyof?T>?=?{
          ????[P?in?K]:?T[P]
          }

          Pick 主要用于提取接口的某幾個(gè)屬性。做過(guò) Todo 工具的同學(xué)都知道,Todo工具只有編輯的時(shí)候才會(huì)填寫(xiě)描述信息,預(yù)覽的時(shí)候只有標(biāo)題和完成狀態(tài),所以我們可以通過(guò) Pick 工具,提取 Todo 接口的兩個(gè)屬性,生成一個(gè)新的類型 TodoPreview。

          interface?Todo?{
          ??title:?string
          ??completed:?boolean
          ??description:?string
          }

          type?TodoPreview?=?Pick"title"?|?"completed">

          const?todo:?TodoPreview?=?{
          ??title:?'Clean?room',
          ??completed:?false
          }
          TodoPreview

          Exclude

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

          Exclude 的作用與之前介紹過(guò)的 Extract 剛好相反,如果 T 中的類型在 U 不存在,則返回,否則拋棄。現(xiàn)在我們那之前的兩個(gè)類舉例,看看 Exclude 的返回結(jié)果。

          interface?Worker?{
          ??name:?string
          ??age:?number
          ??email:?string
          ??salary:?number
          }

          interface?Student?{
          ??name:?string
          ??age:?number
          ??email:?string
          ??grade:?number
          }


          type?ExcludeKeys?=?Exclude
          //?'name'?|?'age'?|?'email'
          ExcludeKeys

          取出的是 Worker 在 Student 中不存在的 salary

          Omit

          type?Omitextends?keyof?any>?=?Pick<
          ??T,?Exclude
          >

          Omit 的作用剛好和 Pick 相反,先通過(guò) Exclude 先取出類型 T 中存在,但是 K 不存在的屬性,然后再由這些屬性構(gòu)造一個(gè)新的類型。還是通過(guò)前面的 Todo 案例來(lái)說(shuō),TodoPreview 類型只需要排除接口的 description 屬性即可,寫(xiě)法上比之前 Pick 精簡(jiǎn)了一些。

          interface?Todo?{
          ??title:?string
          ??completed:?boolean
          ??description:?string
          }

          type?TodoPreview?=?Omit"description">

          const?todo:?TodoPreview?=?{
          ??title:?'Clean?room',
          ??completed:?false
          }
          TodoPreview

          總結(jié)

          如果只是掌握了 TypeScript 的一些基礎(chǔ)類型,可能很難游刃有余的去使用 TypeScript,而且最近 TypeScript 發(fā)布了 4.0 的版本新增了更多功能,想要用好它只能不斷的學(xué)習(xí)和掌握它。希望閱讀本文的朋友都能有所收獲,擺脫 AnyScript。




          推薦閱讀




          我的公眾號(hào)能帶來(lái)什么價(jià)值?(文末有送書(shū)規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 54
          點(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>
                  久久国产成人免费视频 | 操逼网站在线 | 操无码视频 | 性小说亚洲日本视频 | 日本女人一区二区三区 |