<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ìn)階之工具類型&高級(jí)類型

          共 8772字,需瀏覽 18分鐘

           ·

          2020-11-09 03:10

          什么是工具類型

          用 JavaScript 編寫中大型程序是離不開 lodash 工具的,而用 TypeScript 編程同樣離不開工具類型的幫助,工具類型就是類型版的 lodash 。簡(jiǎn)單的來說,就是把已有的類型經(jīng)過類型轉(zhuǎn)換構(gòu)造一個(gè)新的類型。工具類型本身也是類型,得益于泛型的幫助,使其能夠?qū)︻愋瓦M(jìn)行抽象的處理。工具類型主要目的是簡(jiǎn)化類型編程的過程,提高生產(chǎn)力。

          使用工具類型的好處

          先來看看一個(gè)場(chǎng)景,體會(huì)下工具類型帶來什么好處。

          //?一個(gè)用戶接口
          interface?User?{
          ??name:?string
          ??avatar:?string
          ? country:string
          ? friend:{
          ????name:?string
          ????sex:?string
          ??}
          }

          現(xiàn)在業(yè)務(wù)要求 User 接口里的成員都變?yōu)榭蛇x,你會(huì)怎么做?再定義一個(gè)接口,為成員都加上可選修飾符嗎?這種方法確實(shí)可行,但接口里有幾十個(gè)成員呢?此時(shí),工具類型就可以派上用場(chǎng)。

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

          //?此時(shí)PartialUser等同于
          type?PartialUser?=?{
          ??name?:?string?|?undefined;
          ??avatar?:?string?|?undefined;
          ??country?:?string?|?undefined;
          ??friend?:?{
          ????name:?string;
          ????sex:?string;
          ??}?|?undefined;
          }

          通過工具類型的處理,我們得到一個(gè)新的類型。即使成員有成千上百個(gè),我們也只需要一行代碼。由于 friend 成員是對(duì)象,上面的 Partial 處理只對(duì)第一層添加可選修飾符,假如需要將對(duì)象成員內(nèi)的成員也添加可選修飾符,可以使用 Partial 遞歸來解決。

          type?partial?=?{
          ??[K?in?keyof?T]?:?T[K]?extends?object???partial?:?T[K]
          }

          如果你是第一次看到以上的寫法,可能會(huì)很懵逼,不知道發(fā)生了什么操作。不慌,且往下看,或許當(dāng)你看完這篇文章再回過頭來看時(shí),會(huì)發(fā)現(xiàn)原來是這么一回事。

          關(guān)鍵字

          TypeScript 中的一些關(guān)鍵字對(duì)于編寫工具類型必不可缺

          keyof

          語法:「keyof T」 。返回聯(lián)合類型,為 T 的所有 key

          interface?User{
          ??name:?string
          ??age:?number
          }

          type?Man?=?{?
          ??name:string,?
          ??height:?180
          }

          type?ManKeys?=?keyof?Man?//?"name"?|?"height"
          type?UserKeys?=?keyof?User?//?"name"?|?"age"

          typeof

          語法:「typeof T」 。返回 T 的成員的類型

          let?arr?=?['apple',?'banana',?100]
          let?man?=?{
          ??name:?'Jeo',
          ??age:?20,
          ??height:?180
          }

          type?Arr?=?typeof?arr?//?(string?|?number)[]
          type?Man?=?typeof?man?//?{name:?string;?age:?number;?height:?number;}

          infer

          相比上面兩個(gè)關(guān)鍵字, infer 的使用可能會(huì)有點(diǎn)難理解。在有條件類型的 extends 子語句中,允許出現(xiàn) infer 聲明,它會(huì)引入一個(gè)待推斷的類型變量。這個(gè)推斷的類型變量可以在有條件類型的 true 分支中被引用。

          簡(jiǎn)單來說,它可以把類型處理過程的某個(gè)部分抽離出來當(dāng)做類型變量。以下例子需要結(jié)合高級(jí)類型,如果不能理解,可以選擇跳轉(zhuǎn)這部分,把高級(jí)類型看完后再回來。

          下面代碼會(huì)提取函數(shù)類型的返回值類型:

          type?ReturnType?=?T?extends?(...args:?any[])?=>?infer?R???R?:?any;

          (...args: any[]) => infer RFunction 類型的作用是差不多的,這樣寫只是為了能夠在過程中拿到函數(shù)的返回值類型。infer 在這里相當(dāng)于把返回值類型聲明成一個(gè)類型變量,提供給后面的過程使用。

          有條件類型可以嵌套來構(gòu)成一系列的匹配模式,按順序進(jìn)行求值:

          type?Unpacked?=
          ??T?extends?(infer?U)[]???U?:
          ??T?extends?(...args:?any[])?=>?infer?U???U?:
          ??T?extends?Promise???U?:
          ??T;

          type?T0?=?Unpacked<string>;??//?string
          type?T1?=?Unpacked<string[]>;??//?string
          type?T2?=?Unpacked<()?=>?string>;??//?string
          type?T3?=?Unpacked<Promise<string>>;??//?string
          type?T4?=?Unpacked<Promise<string>[]>;??//?Promise
          type?T5?=?UnpackedPromise<string>[]>>;??//?string

          高級(jí)類型

          交叉類型

          語法:「A & B」 ,交叉類型可以把多個(gè)類型合并成一個(gè)新類型,新類型將擁有所有類型的成員。

          interface?Shape?{
          ??size:?string
          ??color:?string
          }
          interface?Brand?{
          ??name:?string
          ??price:?number
          }

          let?clothes:?Shape&Brand?=?{
          ??name:?'Uniqlo',
          ??color:?'blue',
          ??size:?'XL',
          ??price:?200
          }

          聯(lián)合類型

          語法:「typeA | typeB」 ,聯(lián)合類型是包含多種類型的類型,被綁定聯(lián)合類型的成員只需滿足其中一種類型。

          function?pushItem(item:string|number){
          ??let?array:Array<string|number>?=?['apple','banana','cherry']
          ??array.push(item)
          }
          pushItem(10)?//?ok
          pushItem('durian')?//?ok

          通常,刪除用戶信息需要提供 id ,創(chuàng)建用戶則不需要 id 。這種類型應(yīng)該如何定義?如果選擇為 id 字段提供添加可選修飾符的話,那就太不明智了。因?yàn)樵趧h除用戶時(shí),即使不填寫 id 屬性也不會(huì)報(bào)錯(cuò),這不是我們想要的結(jié)果。

          可辨識(shí)聯(lián)合類型能幫助我們解決這個(gè)問題:

          type?UserAction?=?{
          ??action:?'create'
          }|{
          ??id:number
          ??action:?'delete'
          }
          let?userAction:UserAction?=?{
          ??id:?1,
          ??action:?'delete'
          }

          字面量類型

          字?量類型主要分為 真值字?量類型,數(shù)字字?量類型,枚舉字?量類型,?整數(shù)字?量類型、字符串字?量類型。

          const?a:?2333?=?2333?//?ok
          const?b:?0b10?=?2?//?ok
          const?c:?0x514?=?0x514?//?ok
          const?d:?'apple'?=?'apple'?//?ok
          const?e:?true?=?true?//?ok
          const?f:?'apple'?=?'banana'?//?不能將類型“"banana"”分配給類型“"apple"”

          下面以字符串字面量類型作為例子:

          字符串字面量類型允許指定的字符串作為類型。如果使用 JavaScript 的模式中看下面的例子,會(huì)把 level 當(dāng)成一個(gè)值。但在 TypeScript 中,千萬不要用這種思維去看待, level 表示的就是一個(gè)字符串 coder 的類型,被綁定這個(gè)類型的變量,它的值只能是 coder

          type?Level?=?'coder'
          let?level:Level?=?'coder'?//?ok
          let?level2:Level?=?'programmer'?//?不能將類型“"programmer"”分配給類型“"coder"”

          字符串和聯(lián)合類型搭配,可以實(shí)現(xiàn)類似枚舉類型的字符串

          type?Level?=?'coder'?|?'leader'?|?'boss'
          function?getWork(level:?Level){
          ??if(level?===?'coder'){
          ????console.log('打代碼、摸魚')
          ??}else?if(level?===?'leader'){
          ????console.log('造輪子、架構(gòu)')
          ??}else?if(level?===?'boss'){
          ????console.log('喝茶、談生意')
          ??}
          }
          getWork('coder')
          getWork('user')?//?類型“"user"”的參數(shù)不能賦給類型“Level”的參數(shù)

          索引類型

          語法:「T[K]」 ,使用索引類型,編譯器就能夠檢查使用動(dòng)態(tài)屬性名的代碼。在 JavaScript 中,對(duì)象可以用屬性名獲取值,而在 TypeScript 中,這一切被抽象化,變成通過索引獲取類型。就像 person[name] 被抽象成類型 Person[name] ,在以下例子中代表的就是 string 類型。

          interface?Person?{
          ??name:?string;
          ??age:?number;
          }
          let?person:?Person?=?{
          ??name:?'Jeo',
          ??age:?20
          }
          let?name?=?person['name']?//?'Jeo'
          type?str?=?Person['name']?//?string

          我們可以在普通的上下文里使用 T[K] ,只要確保類型變量 KT 的索引即可

          function?getProperty<T,?K?extends?keyof?T>(o:?T,?name:?K):?T[K]?{
          ??return?o[name];?//?o[name]?is?of?type?T[K]
          }

          getProperty 里的 o: Tname: K ,意味著 o[name]: T[K]

          let?name:?string?=?getProperty(person,?'name');
          let?age:?number?=?getProperty(person,?'age');
          let?unknown?=?getProperty(person,?'unknown');?//?類型“"unknown"”的參數(shù)不能賦給類型“"name"?|?"age"”的參數(shù)

          K 不僅可以傳成員,成員的字符串聯(lián)合類型也是有效的

          type?Union?=?Person[keyof?Person]?//?"string"?|?"number"

          映射類型

          語法:「[K in Keys]」 。TypeScript 提供了從舊類型中創(chuàng)建新類型的一種方式 。在映射類型里,新類型以相同的形式去轉(zhuǎn)換舊類型里每個(gè)屬性。根據(jù) Keys 來創(chuàng)建類型, Keys 有效值為 string | number | symbol 或 聯(lián)合類型。

          type?Keys?=?'name'|10
          type?User?=?{
          ??[K?in?Keys]:?string
          }

          該語法可以理解為內(nèi)部使用了循環(huán)

          • K: 依次綁定到每個(gè)屬性,相當(dāng)于 Keys 的項(xiàng)
          • Keys: 包含要迭代的屬性名的集合

          因此以上的例子等同于:

          type?User?=?{
          ??name:?string;
          ??10:?string;
          }

          需要注意的是這個(gè)語法描述的是類型而非成員。若想添加額外的成員,需使用交叉類型:

          //?這樣使用
          type?ReadonlyWithNewMember?=?{
          ??readonly?[P?in?keyof?T]:?T[P];
          }?&?{?newMember:?boolean?}
          //?不要這樣使用
          //?這會(huì)報(bào)錯(cuò)!
          type?ReadonlyWithNewMember?=?{
          ??readonly?[P?in?keyof?T]:?T[P];
          ??newMember:?boolean;
          }

          在真正應(yīng)用中,映射類型結(jié)合索引訪問類型是一個(gè)很好的搭配。因?yàn)檗D(zhuǎn)換過程會(huì)基于一些已存在的類型,且按照一定的方式轉(zhuǎn)換字段。你可以把這過程理解為 JavaScript 中數(shù)組的 map 方法,在原本的基礎(chǔ)上擴(kuò)展元素( TypeScript 中指類型),當(dāng)然這種理解過程可能有點(diǎn)粗糙。

          文章開頭的 Partial 工具類型正是使用這種搭配,為原有的類型添加可選修飾符。

          條件類型

          語法:「T extends U ? X : Y」 ,若 T 能夠賦值給 U ,那么類型是 X ,否則為 Y 。條件類型以條件表達(dá)式推斷類型關(guān)系,選擇其中一個(gè)分支。相對(duì)上面的類型,條件類型很好理解,類似 JavaScript 中的三目運(yùn)算符。

          再來看看文章開頭遞歸的操作,你就會(huì)發(fā)現(xiàn)能看懂這段處理過程。過程:使用映射類型遍歷,判斷 T[K] 屬于 object 類型,則把 T[K] 傳入 partial 遞歸,否則返回類型 T[K]

          type?partial?=?{
          ??[K?in?keyof?T]?:?T[K]?extends?object???partial?:?T[K]
          }

          小結(jié)

          關(guān)于一些常用的高級(jí)類型相信大家都了解得差不多,下面將應(yīng)用這些類型來編寫一個(gè)工具類型。

          該工具類型實(shí)現(xiàn)的功能為篩選出兩個(gè) interface 的公共成員:

          interface?PersonA{
          ??name:?string
          ??age:?number
          ??boyfriend:?string
          ??car:?{
          ????type:?'Benz'
          ??}
          }

          interface?PersonB{
          ??name:?string
          ??age:?string
          ??girlfriend:?string
          ??car:?{
          ????type:?'bicycle'
          ??}
          }

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

          type?Common?=?{
          ??[K?in?Filter]:?A[K]?extends?B[K]???A[K]?:?A[K]|B[K]
          }

          通過 Filter 篩選出公共的成員聯(lián)合類型 "name"|"age" 作為映射類型的集合,公共部分可能會(huì)存在類型不同的情況,因此要為成員保留兩者的類型。

          type?CommonMember?=?Common

          //?等同于
          type?CommonMember?=?{
          ??name:?string;
          ??age:?string?|?number;
          ??car:?{
          ????type:?"Benz";
          ??}?|?{
          ????type:?"bicycle";
          ??};
          }

          內(nèi)置工具類型

          為了滿足常見的類型轉(zhuǎn)換需求, TypeScript 也提供一些內(nèi)置工具類型,這些類型是全局可見的。

          Partial

          構(gòu)造類型 T ,并將它所有的屬性設(shè)置為可選的。它的返回類型表示輸入類型的所有子類型。

          interface?Todo?{
          ??title:?string;
          ??description:?string;
          }

          function?updateTodo(todo:?Todo,?fieldsToUpdate:?Partial)?{
          ??return?{?...todo,?...fieldsToUpdate?};
          }

          const?todo1?=?{
          ??title:?'organize?desk',
          ??description:?'clear?clutter',
          };

          const?todo2?=?updateTodo(todo1,?{
          ??description:?'throw?out?trash',
          });

          Readonly

          構(gòu)造類型T,并將它所有的屬性設(shè)置為readonly,也就是說構(gòu)造出的類型的屬性不能被再次賦值。

          interface?Todo?{
          ??title:?string;
          }

          const?todo:?Readonly?=?{
          ??title:?'Delete?inactive?users',
          };

          todo.title?=?'Hello';?//?Error:?cannot?reassign?a?readonly?property

          Record

          構(gòu)造一個(gè)類型,其屬性名的類型為K,屬性值的類型為T。這個(gè)工具可用來將某個(gè)類型的屬性映射到另一個(gè)類型上。

          interface?PageInfo?{
          ??title:?string;
          }

          type?Page?=?'home'?|?'about'?|?'contact';

          const?x:?Record?=?{
          ??about:?{?title:?'about'?},
          ??contact:?{?title:?'contact'?},
          ??home:?{?title:?'home'?},
          };

          Pick

          從類型T中挑選部分屬性K來構(gòu)造類型。

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

          type?TodoPreview?=?Pick'title'?|?'completed'>;

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

          Omit

          從類型T中剔除部分屬性K來構(gòu)造類型,與Pick相反。

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

          type?TodoPreview?=?Omit'title'?|?'completed'>;

          const?todo:?TodoPreview?=?{
          ??description:?'I?am?description'
          };

          Exclude

          從類型T中剔除所有可以賦值給U的屬性,然后構(gòu)造一個(gè)類型。

          type?T0?=?Exclude<"a"?|?"b"?|?"c",?"a">;??//?"b"?|?"c"
          type?T1?=?Exclude<"a"?|?"b"?|?"c",?"a"?|?"b">;??//?"c"
          type?T2?=?Exclude<string?|?number?|?(()?=>?void),?Function>;??//?string?|?number

          Extract

          從類型T中提取所有可以賦值給U的類型,然后構(gòu)造一個(gè)類型。

          type?T0?=?Extract<"a"?|?"b"?|?"c",?"a"?|?"f">;??//?"a"
          type?T1?=?Extract<string?|?number?|?(()?=>?void),?Function>;??//?()?=>?void

          NonNullable

          從類型T中剔除null和undefined,然后構(gòu)造一個(gè)類型。

          type?T0?=?NonNullable<string?|?number?|?undefined>;??//?string?|?number
          type?T1?=?NonNullable<string[]?|?null?|?undefined>;??//?string[]

          ReturnType

          由函數(shù)類型T的返回值類型構(gòu)造一個(gè)類型。

          type?T0?=?ReturnType<()?=>?string>;??//?string
          type?T1?=?ReturnType<(s:?string)?=>?void>;??//?void
          type?T2?=?ReturnType<(()?=>?T)>;??//?{}
          type?T3?=?ReturnType<(extends?U,?U?extends?number[]>()?=>?T
          )>;??//?number[]
          type?T5?=?ReturnType<any>;??//?any
          type?T6?=?ReturnType<never>;??//?any
          type?T7?=?ReturnType<string>;??//?Error
          type?T8?=?ReturnType<Function>;??//?Error

          InstanceType

          由構(gòu)造函數(shù)類型T的實(shí)例類型構(gòu)造一個(gè)類型。

          class?C?{
          ??x?=?0;
          ??y?=?0;
          }

          type?T0?=?InstanceType<typeof?C>;??//?C
          type?T1?=?InstanceType<any>;??//?any
          type?T2?=?InstanceType;??//?any
          type?T3?=?InstanceType<string>;??//?Error
          type?T4?=?InstanceType<Function>;??//?Error

          let?t0:T0?=?{
          ??x:?10,
          ??y:?2
          }

          Required

          構(gòu)造一個(gè)類型,使類型T的所有屬性為required。

          interface?Props?{
          ??a?:?number;
          ??b?:?string;
          };

          const?obj:?Props?=?{?a:?5?};?//?OK

          const?obj2:?Required?=?{?a:?5?};?//?Error:?property?'b'?missing

          寫在最后

          除了介紹編寫工具類型所需要具備的一些知識(shí)點(diǎn),以及 TypeScript 內(nèi)置的工具類型。更重要的是抽象思維能力,不難發(fā)現(xiàn)上面的例子大部分沒有具體的值運(yùn)算,都是使用類型在編程。想要理解這些知識(shí),必須要進(jìn)入到抽象邏輯里思考。還有高級(jí)類型的搭配和類型轉(zhuǎn)換的處理,也要通過大量的實(shí)踐才能玩好。說實(shí)話,自己學(xué)習(xí)這些知識(shí)時(shí),真正感受到 TypeScript 的深不可測(cè),也了解到自身的不足之處。突然想起在某篇文章的一句話:技術(shù)是無止盡的,接觸的越多,越能感到自己的渺小。

          參考資料

          Typescript Hankbook(中文版)


          瀏覽 60
          點(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>
                  尤物在线亚洲欧美久91xyz | 四虎成人午夜影视亚州精品 | 亚洲成人欧美成人 | www.亚洲天堂 | 青娱乐 超碰 |