<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 高級類型

          共 10867字,需瀏覽 22分鐘

           ·

          2021-03-03 12:48

          作者:Shenfq
          來源:SegmentFault 思否社區(qū)




          前言


          對于有 JavaScript 基礎(chǔ)的同學(xué)來說,入門 TypeScript 其實很容易,只需要簡單掌握其基礎(chǔ)的類型系統(tǒng)就可以逐步將 JS 應(yīng)用過渡到 TS 應(yīng)用。


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

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


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




          泛型


          在講解高級類型之前,我們需要先簡單理解泛型是什么。


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


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


          泛型通過一對尖括號來表示(<>),尖括號內(nèi)的字符被稱為類型變量,這個變量用來表示類型。


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


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


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



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





          高級類型


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


          交叉類型(&)


          交叉類型說簡單點就是將多個類型合并成一個類型,個人感覺叫做「合并類型」更合理一點,其語法規(guī)則和邏輯 “與” 的符號一致。


          T & U


          假如,我現(xiàn)在有兩個類,一個按鈕,一個超鏈接,現(xiàn)在我需要一個帶有超鏈接的按鈕,就可以使用交叉類型來實現(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)合類型的語法規(guī)則和邏輯 “或” 的符號一致,表示其類型為連接的多個類型中的任意一個。


          T | U


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


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

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


          類型別名(type)


          前面提到的交叉類型與聯(lián)合類型如果有多個地方需要使用,就需要通過類型別名的方式,給這兩種類型聲明一個別名。類型別名與聲明變量的語法類似,只需要把 const、let 換成 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 ,用于獲取一個接口中 Key 的聯(lián)合類型。


          interface Button {
              type: string
              text: string
          }

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


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


          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 類型會自動修改 
          interface Button {
              type: keyof ButtonTypes
              text: string
          }


          類型約束(extends)


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


          type BaseType = string | number | boolean

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



          如果我們傳入一個對象就會有問題。



          extends 經(jīng)常與 keyof 一起使用,例如我們有一個方法專門用來獲取對象的值,但是這個對象并不確定,我們就可以使用 extends 和 keyof 進行約束。


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

          const obj = { a: 1 }
          const a = getValue(obj, 'a')



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


          類型映射(in)


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


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

          interface Obj {
            a: string
            b: string
          }

          type ReadOnlyObj = Readonly<Obj>



          我們可以結(jié)構(gòu)下這個邏輯,首先 keyof Obj 得到一個聯(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'];
          }


          最后就可以得到一個新的接口。


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


          條件類型(U ? X : Y)


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


          T extends U ? X : Y


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


          type Extract<T, U> = T extends U ? T : never;


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


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

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


          type CommonKeys = Extract<keyof Worker, keyof Student>
          // 'name' | 'age' | 'email'





          工具泛型


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



          Partial


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


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


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


          import React from 'react'

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

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


          Required


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


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


          Record


          type Record<K extends keyof any, T> = {
              [P in K]: T
          }


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



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


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


          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 Pick<T, K extends keyof T> = {
              [P in K]: T[P]
          }


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


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

          type TodoPreview = Pick<Todo, "title" | "completed">

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



          Exclude


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


          Exclude 的作用與之前介紹過的 Extract 剛好相反,如果 T 中的類型在 U 不存在,則返回,否則拋棄。現(xiàn)在我們拿之前的兩個類舉例,看看 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<keyof Worker, keyof Student>
          // 'salary'



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


          Omit


          type Omit<T, K extends keyof any> = Pick<
            T, Exclude<keyof T, K>
          >


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


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

          type TodoPreview = Omit<Todo, "description">

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





          總結(jié)


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




          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 23
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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电影网 | 日皮精品视频免费 | 3344gc在线观看入口 | 免费在线看黄的网站 | 国产精品自拍在线 |