<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í)技巧(實(shí)用!)

          共 17253字,需瀏覽 35分鐘

           ·

          2020-12-27 16:26

          高翔,微醫(yī)云服務(wù)團(tuán)隊(duì)前端工程師,喜歡美食,熱愛(ài)技術(shù),喜歡折騰。

          前言

          在 2020 年的今天,TS 已經(jīng)越來(lái)越火,不管是服務(wù)端(Node.js),還是前端框架(Angular、Vue3),都有越來(lái)越多的項(xiàng)目使用 TS 開(kāi)發(fā),作為前端程序員,TS 已經(jīng)成為一項(xiàng)必不可少的技能,本文旨在介紹 TS 中的一些高級(jí)技巧,提高大家對(duì)這門(mén)語(yǔ)言更深層次的認(rèn)知。

          Typescript 簡(jiǎn)介

          • ECMAScript 的超集 (stage 3)
          • 編譯期的類(lèi)型檢查
          • 不引入額外開(kāi)銷(xiāo)(零依賴(lài),不擴(kuò)展 js 語(yǔ)法,不侵入運(yùn)行時(shí))
          • 編譯出通用的、易讀的 js 代碼

          Typescript = Type + ECMAScript + Babel-Lite

          Typescript 設(shè)計(jì)目標(biāo): https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

          為什么使用 Typescript

          • 增加了代碼的可讀性和可維護(hù)性
          • 減少運(yùn)行時(shí)錯(cuò)誤,寫(xiě)出的代碼更加安全,減少 BUG
          • 享受到代碼提示帶來(lái)的好處
          • 重構(gòu)神器

          基礎(chǔ)類(lèi)型

          • boolean
          • number
          • string
          • array
          • tuple
          • enum
          • void
          • null & undefined
          • any & unknown
          • never

          anyunknown 的區(qū)別

          • any: 任意類(lèi)型
          • unknown: 未知的類(lèi)型

          任何類(lèi)型都能分配給 unknown,但 unknown 不能分配給其他基本類(lèi)型,而 any 啥都能分配和被分配。

          let?foo:?unknown

          foo?=?true?//?ok
          foo?=?123?//ok

          foo.toFixed(2)?//?error

          let?foo1:?string?=?foo?//?error
          let?bar:?any

          bar?=?true?//?ok
          bar?=?123?//ok

          foo.toFixed(2)?//?ok

          let?bar1:string??=?bar?//?ok

          可以看到,用了 any 就相當(dāng)于完全丟失了類(lèi)型檢查,所以大家盡量少用 any,對(duì)于未知類(lèi)型可以用 unknown

          unknown 的正確用法

          我們可以通過(guò)不同的方式將 unknown 類(lèi)型縮小為更具體的類(lèi)型范圍:

          function?getLen(value:?unknown):?number?{
          ??if?(typeof?value?===?'string')?{
          ????//?因?yàn)轭?lèi)型保護(hù)的原因,此處?value?被判斷為?string?類(lèi)型
          ???return?value.length
          ??}
          ??
          ??return?0
          }

          這個(gè)過(guò)程叫類(lèi)型收窄(type narrowing)。

          never

          never 一般表示哪些用戶(hù)無(wú)法達(dá)到的類(lèi)型。在最新的 typescript 3.7 中,下面代碼會(huì)報(bào)錯(cuò):

          //?never?用戶(hù)控制流分析
          function?neverReach?():?never?{
          ??throw?new?Error('an?error')
          }

          const?x?=?2

          neverReach()

          x.toFixed(2)??//?x?is?unreachable

          never 還可以用于聯(lián)合類(lèi)型的 幺元

          type?T0?=?string?|?number?|?never?//?T0?is?string?|?number

          函數(shù)類(lèi)型

          幾種函數(shù)類(lèi)型的返回值類(lèi)型寫(xiě)法

          function?fn():?number?{
          ??return?1
          }

          const?fn?=?function?():?number?{
          ??return?1
          }

          const?fn?=?():?number?=>?{
          ??return?1
          }

          const?obj?=?{
          ??fn?():?number?{
          ????return?1
          ??}
          }

          () 后面添加返回值類(lèi)型即可。

          函數(shù)類(lèi)型

          ts 中也有函數(shù)類(lèi)型,用來(lái)描述一個(gè)函數(shù):

          type?FnType?=?(x:?number,?y:?number)?=>?number

          完整的函數(shù)寫(xiě)法

          let?myAdd:?(x:?number,?y:?number)?=>?number?=?function(x:?number,?y:?number):?number?{
          ??return?x?+?y
          }

          //?使用?FnType?類(lèi)型
          let?myAdd:?FnType?=?function(x:?number,?y:?number):?number?{
          ??return?x?+?y
          }

          //?ts?自動(dòng)推導(dǎo)參數(shù)類(lèi)型
          let?myAdd:?FnType?=?function(x,?y)?{
          ??return?x?+?y
          }

          函數(shù)重載?

          js因?yàn)槭莿?dòng)態(tài)類(lèi)型,本身不需要支持重載,ts為了保證類(lèi)型安全,支持了函數(shù)簽名的類(lèi)型重載。即:

          多個(gè)重載簽名和一個(gè)實(shí)現(xiàn)簽名

          //?重載簽名(函數(shù)類(lèi)型定義)
          function?toString(x:?string):?string;
          function?toString(x:?number):?string;

          //?實(shí)現(xiàn)簽名(函數(shù)體具體實(shí)現(xiàn))
          function?toString(x:?string?|?number)?{
          ??return?String(x)
          }

          let?a?=?toString('hello')?//?ok
          let?b?=?toString(2)?//?ok
          let?c?=?toString(true)?//?error

          如果定義了重載簽名,則實(shí)現(xiàn)簽名對(duì)外不可見(jiàn)

          function?toString(x:?string):?string;

          function?toString(x:?number):?string?{
          ??return?String(x)
          }

          len(2)?//?error

          實(shí)現(xiàn)簽名必須兼容重載簽名

          function?toString(x:?string):?string;
          function?toString(x:?number):?string;?//?error

          //?函數(shù)實(shí)現(xiàn)
          function?toString(x:?string)?{
          ??return?String(x)
          }

          重載簽名的類(lèi)型不會(huì)合并

          //?重載簽名(函數(shù)類(lèi)型定義)
          function?toString(x:?string):?string;
          function?toString(x:?number):?string;

          //?實(shí)現(xiàn)簽名(函數(shù)體具體實(shí)現(xiàn))
          function?toString(x:?string?|?number)?{
          ??return?String(x)
          }

          function?stringOrNumber(x):?string?|?number?{
          ??return?x???''?:?0
          }

          //?input?是?string?和?number?的聯(lián)合類(lèi)型
          //?即?string?|?number
          const?input?=?stringOrNumber(1)

          toString('hello')?//?ok
          toString(2)?//?ok
          toString(input)?//?error

          類(lèi)型推斷

          ts 中的類(lèi)型推斷是非常強(qiáng)大,而且其內(nèi)部實(shí)現(xiàn)也是非常復(fù)雜的。

          基本類(lèi)型推斷:

          //?ts?推導(dǎo)出?x?是?number?類(lèi)型
          let?x?=?10

          對(duì)象類(lèi)型推斷:

          // ts 推斷出 myObj 的類(lèi)型:myObj:?{ x: number; y: string; z: boolean;?}
          const?myObj?=?{
          ??x:?1,
          ??y:?'2',
          ??z:?true
          }

          函數(shù)類(lèi)型推斷:

          //?ts?推導(dǎo)出函數(shù)返回值是?number?類(lèi)型
          function?len?(str:?string)?{
          ??return?str.length
          }

          上下文類(lèi)型推斷:

          //?ts?推導(dǎo)出?event?是?ProgressEvent?類(lèi)型
          const?xhr?=?new?XMLHttpRequest()
          xhr.onload?=?function?(event)?{}

          所以有時(shí)候?qū)τ谝恍┖?jiǎn)單的類(lèi)型可以不用手動(dòng)聲明其類(lèi)型,讓 ts 自己去推斷。

          類(lèi)型兼容性

          typescript 的子類(lèi)型是基于 結(jié)構(gòu)子類(lèi)型 的,只要結(jié)構(gòu)可以兼容,就是子類(lèi)型。(Duck Type)

          class?Point?{
          ??x:?number
          }

          function?getPointX(point:?Point)?{
          ??return?point.x
          }

          class?Point2?{
          ??x:?number
          }

          let?point2?=?new?Point2()

          getPointX(point2)?//?OK

          javac++ 等傳統(tǒng)靜態(tài)類(lèi)型語(yǔ)言是基于 名義子類(lèi)型 的,必須顯示聲明子類(lèi)型關(guān)系(繼承),才可以兼容。

          public?class?Main?{
          ??public?static?void?main?(String[]?args)?{
          ????getPointX(new?Point());?//?ok
          ????getPointX(new?ChildPoint());?//?ok
          ????getPointX(new?Point1());??//?error
          ??}

          ??public?static?void?getPointX?(Point?point)?{
          ????System.out.println(point.x);
          ??}

          ??static?class?Point?{
          ????public?int?x?=?1;
          ??}

          ??static?class?Point2?{
          ????public?int?x?=?2;
          ??}
          ????
          ??static?class?ChildPoint?extends?Point?{
          ????public?int?x?=?3;
          ??}
          }

          對(duì)象子類(lèi)型

          子類(lèi)型中必須包含源類(lèi)型所有的屬性和方法:

          function?getPointX(point:?{?x:?number?})?{
          ??return?point.x
          }

          const?point?=?{
          ?x:?1,
          ??y:?'2'
          }

          getPointX(point)?//?OK

          注意: 如果直接傳入一個(gè)對(duì)象字面量是會(huì)報(bào)錯(cuò)的:

          function?getPointX(point:?{?x:?number?})?{
          ??return?point.x
          }

          getPointX({?x:?1,?y:?'2'?})?//?error

          這是 ts 中的另一個(gè)特性,叫做:? excess property check? ,當(dāng)傳入的參數(shù)是一個(gè)對(duì)象字面量時(shí),會(huì)進(jìn)行額外屬性檢查。

          函數(shù)子類(lèi)型

          介紹函數(shù)子類(lèi)型前先介紹一下逆變協(xié)變的概念,逆變協(xié)變并不是 TS 中獨(dú)有的概念,在其他靜態(tài)語(yǔ)言中也有相關(guān)理念。

          在介紹之前,先假設(shè)一個(gè)問(wèn)題,約定如下標(biāo)記:

          • A?? B 表示 A 是 B 的子類(lèi)型,A 包含 B 的所有屬性和方法。
          • A => B 表示以 A 為參數(shù),B 為返回值的方法。(param: A) => B

          如果我們現(xiàn)在有三個(gè)類(lèi)型 AnimalDogWangCai(旺財(cái)) ,那么肯定存在下面的關(guān)系:

          WangCai???Dog???Animal?//?即旺財(cái)屬于狗屬于動(dòng)物

          問(wèn)題:以下哪種類(lèi)型是 Dog => Dog 的子類(lèi)呢?

          • WangCai => WangCai
          • WangCai => Animal
          • Animal? => Animal
          • Animal? => WangCai

          從代碼來(lái)看解答

          class?Animal?{
          ??sleep:?Function
          }

          class?Dog?extends?Animal?{
          ??//?吠
          ??bark:?Function
          }

          class?WangCai?extends?Dog?{
          ??dance:?Function
          }


          function?getDogName?(cb:?(dog:?Dog)?=>?Dog)?{
          ??const?dog?=?cb(new?Dog())
          ??dog.bark()
          }

          //?對(duì)于入?yún)?lái)說(shuō),WangCai 是 Dog 的子類(lèi),Dog 類(lèi)上沒(méi)有 dance 方法, 產(chǎn)生異常。
          //?對(duì)于出參來(lái)說(shuō),WangCai 類(lèi)繼承了 Dog 類(lèi),肯定會(huì)有 bark 方法
          getDogName((wangcai:?WangCai)?=>?{
          ??wangcai.dance()
          ??return?new?WangCai()
          })

          //?對(duì)于入?yún)?lái)說(shuō),WangCai 是 Dog 的子類(lèi),Dog 類(lèi)上沒(méi)有 dance 方法, 產(chǎn)生異常。
          //?對(duì)于出參來(lái)說(shuō),Animal 類(lèi)上沒(méi)有 bark 方法, 產(chǎn)生異常。
          getDogName((wangcai:?WangCai)?=>?{
          ??wangcai.dance()
          ??return?new?Animal()
          })

          //?對(duì)于入?yún)?lái)說(shuō),Animal 類(lèi)是 Dog 的父類(lèi),Dog 類(lèi)肯定有 sleep 方法。
          //?對(duì)于出參來(lái)說(shuō),WangCai 類(lèi)繼承了 Dog 類(lèi),肯定會(huì)有 bark 方法
          getDogName((animal:?Animal)?=>?{
          ??animal.sleep()
          ??return?new?WangCai()
          })

          //?對(duì)于入?yún)?lái)說(shuō),Animal 類(lèi)是 Dog 的父類(lèi),Dog 類(lèi)肯定有 sleep 方法。
          //?對(duì)于出參來(lái)說(shuō),Animal 類(lèi)上沒(méi)有 bark 方法, 產(chǎn)生異常。
          getDogName((animal:?Animal)?=>?{
          ??animal.sleep()
          ??return?new?Animal()
          })

          可以看到只有 Animal => WangCai 才是 Dog => Dog 的子類(lèi)型,可以得到一個(gè)結(jié)論,對(duì)于函數(shù)類(lèi)型來(lái)說(shuō),函數(shù)參數(shù)的類(lèi)型兼容是反向的,我們稱(chēng)之為 逆變 ,返回值的類(lèi)型兼容是正向的,稱(chēng)之為 協(xié)變

          逆變與協(xié)變的例子只說(shuō)明了函數(shù)參數(shù)只有一個(gè)時(shí)的情況,如果函數(shù)參數(shù)有多個(gè)時(shí)該如何區(qū)分?

          其實(shí)函數(shù)的參數(shù)可以轉(zhuǎn)化為 Tuple 的類(lèi)型兼容性:

          type?Tuple1?=?[string,?number]
          type?Tuple2?=?[string,?number,?boolean]

          let?tuple1:?Tuple1?=?['1',?1]
          let?tuple2:?Tuple2?=?['1',?1,?true]

          let?t1:?Tuple1?=?tuple2?//?ok
          let?t2:?Tuple2?=?tuple1?//?error

          可以看到 Tuple2 => Tuple1 ,即長(zhǎng)度大的是長(zhǎng)度小的子類(lèi)型,再由于函數(shù)參數(shù)的逆變特性,所以函數(shù)參數(shù)少的可以賦值給參數(shù)多的(參數(shù)從前往后需一一對(duì)應(yīng)),從數(shù)組的 forEach 方法就可以看出來(lái):

          [1,?2].forEach((item,?index)?=>?{
          ?console.log(item)
          })?//?ok

          [1,?2].forEach((item,?index,?arr,?other)?=>?{
          ?console.log(other)
          })?//?error

          高級(jí)類(lèi)型

          聯(lián)合類(lèi)型與交叉類(lèi)型

          聯(lián)合類(lèi)型(union type)表示多種類(lèi)型的 “或” 關(guān)系

          function?genLen(x:?string?|?any[])?{
          ??return?x.length
          }

          genLen('')?//?ok
          genLen([])?//?ok
          genLen(1)?//?error

          交叉類(lèi)型表示多種類(lèi)型的 “與” 關(guān)系

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

          interface?Animal?{
          ??name:?string
          ??color:?string
          }

          const?x:?Person?&?Animal?=?{
          ??name:?'x',
          ??age:?1,
          ??color:?'red
          }

          使用聯(lián)合類(lèi)型表示枚舉

          type?Position?=?'UP'?|?'DOWN'?|?'LEFT'?|?'RIGHT'

          const?position:?Position?=?'UP'

          可以避免使用 enum 侵入了運(yùn)行時(shí)。

          類(lèi)型保護(hù)

          ts 初學(xué)者很容易寫(xiě)出下面的代碼:

          function?isString?(value)?{
          ??return?Object.prototype.toString.call(value)?===?'[object?String]'
          }

          function?fn?(x:?string?|?number)?{
          ??if?(isString(x))?{
          ????return?x.length?// error 類(lèi)型“string | number”上不存在屬性“l(fā)ength”。
          ??}?else?{
          ????//?.....
          ??}
          }

          如何讓 ts 推斷出來(lái)上下文的類(lèi)型呢?

          1. 使用 ts 的 is 關(guān)鍵詞

          function?isString?(value:?unknown):?value?is?string?{
          ??return?Object.prototype.toString.call(value)?===?'[object?String]'
          }

          function?fn?(x:?string?|?number)?{
          ??if?(isString(x))?{
          ????return?x.length
          ??}?else?{
          ????//?.....
          ??}
          }

          2. typeof 關(guān)鍵詞

          在 ts 中,代碼實(shí)現(xiàn)中的 typeof 關(guān)鍵詞能夠幫助 ts 判斷出變量的基本類(lèi)型:

          function?fn?(x:?string?|?number)?{
          ??if?(typeof?x?===?'string')?{?//?x?is?string
          ????return?x.length
          ??}?else?{?//?x?is?number
          ????//?.....
          ??}
          }

          3. instanceof 關(guān)鍵詞

          在 ts 中,instanceof 關(guān)鍵詞能夠幫助 ts 判斷出構(gòu)造函數(shù)的類(lèi)型:

          function?fn1?(x:?XMLHttpRequest?|?string)?{
          ??if?(x?instanceof?XMLHttpRequest)?{?//?x?is?XMLHttpRequest
          ????return?x.getAllResponseHeaders()
          ??}?else?{?//?x?is?string
          ????return?x.length
          ??}
          }

          4. 針對(duì) null 和 undefined 的類(lèi)型保護(hù)

          在條件判斷中,ts 會(huì)自動(dòng)對(duì) null 和 undefined 進(jìn)行類(lèi)型保護(hù):

          function?fn2?(x?:?string)?{
          ??if?(x)?{
          ????return?x.length
          ??}
          }

          5. 針對(duì) null 和 undefined 的類(lèi)型斷言

          如果我們已經(jīng)知道的參數(shù)不為空,可以使用 ! 來(lái)手動(dòng)標(biāo)記:

          function?fn2?(x?:?string)?{
          ??return?x!.length
          }

          typeof 關(guān)鍵詞

          typeof 關(guān)鍵詞除了做類(lèi)型保護(hù),還可以從實(shí)現(xiàn)推出類(lèi)型,

          注意:此時(shí)的 typeof 是一個(gè)類(lèi)型關(guān)鍵詞,只可以用在類(lèi)型語(yǔ)法中。

          function?fn(x:?string)?{
          ??return?x.length
          }

          const?obj?=?{
          ??x:?1,
          ??y:?'2'
          }

          type?T0?=?typeof?fn?//?(x:?string)?=>?number
          type?T1?=?typeof?obj?//?{x:?number;?y:?string?}

          keyof 關(guān)鍵詞

          keyof 也是一個(gè) 類(lèi)型關(guān)鍵詞 ,可以用來(lái)取得一個(gè)對(duì)象接口的所有 key 值:

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

          type?PersonAttrs?=?keyof?Person?//?'name'?|?'age'

          in 關(guān)鍵詞

          in 也是一個(gè) 類(lèi)型關(guān)鍵詞, 可以對(duì)聯(lián)合類(lèi)型進(jìn)行遍歷,只可以用在 type 關(guān)鍵詞下面。

          type?Person?=?{
          ??[key?in?'name'?|?'age']:?number
          }

          //?{?name:?number;?age:?number;?}

          [ ] 操作符

          使用 [] 操作符可以進(jìn)行索引訪(fǎng)問(wèn),也是一個(gè) 類(lèi)型關(guān)鍵詞

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

          type?x?=?Person['name']?//?x?is?string

          一個(gè)小栗子

          寫(xiě)一個(gè)類(lèi)型復(fù)制的類(lèi)型工具:

          type?Copy?=?{
          ??[key?in?keyof?T]:?T[key]
          }

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

          type?Person1?=?Copy

          泛型

          泛型相當(dāng)于一個(gè)類(lèi)型的參數(shù),在 ts 中,泛型可以用在 類(lèi)接口方法類(lèi)型別名 等實(shí)體中。

          小試牛刀

          function?createList<T>():?T[]?{
          ??return?[]?as?T[]
          }

          const?numberList?=?createList<number>()?//?number[]
          const?stringList?=?createList<string>()?//?string[]

          有了泛型的支持,createList 方法可以傳入一個(gè)類(lèi)型,返回有類(lèi)型的數(shù)組,而不是一個(gè) any[]

          泛型約束

          如果我們只希望 createList 函數(shù)只能生成指定的類(lèi)型數(shù)組,該如何做,可以使用 extends 關(guān)鍵詞來(lái)約束泛型的范圍和形狀。

          type?Lengthwise?=?{
          ??length:?number
          }

          function?createList<T?extends?number?|?Lengthwise>():?T[]?{
          ??return?[]?as?T[]
          }

          const?numberList?=?createList<number>()?//?ok
          const?stringList?=?createList<string>()?//?ok
          const?arrayList?=?createList<any[]>()?//?ok
          const?boolList?=?createList<boolean>()?//?error

          any[] 是一個(gè)數(shù)組類(lèi)型,數(shù)組類(lèi)型是有 length 屬性的,所以 ok。string 類(lèi)型也是有 length 屬性的,所以 ok。但是 boolean 就不能通過(guò)這個(gè)約束了。

          條件控制

          extends 除了做約束類(lèi)型,還可以做條件控制,相當(dāng)于與一個(gè)三元運(yùn)算符,只不過(guò)是針對(duì) 類(lèi)型 的。

          表達(dá)式T extends U ? X : Y

          含義:如果 T 可以被分配給 U,則返回 X,否則返回 Y。一般條件下,如果 T 是 U 的子類(lèi)型,則認(rèn)為 T 可以分配給 U,例如:

          type?IsNumber?=?T?extends?number???true?:?false

          type?x?=?IsNumber<string>??//?false

          映射類(lèi)型

          映射類(lèi)型相當(dāng)于一個(gè)類(lèi)型的函數(shù),可以做一些類(lèi)型運(yùn)算,輸入一個(gè)類(lèi)型,輸出另一個(gè)類(lèi)型,前文我們舉了個(gè) Copy 的例子。

          幾個(gè)內(nèi)置的映射類(lèi)型

          //?每一個(gè)屬性都變成可選
          type?Partial?=?{
          ??[P?in?keyof?T]?:?T[P]
          }

          //?每一個(gè)屬性都變成只讀
          type?Readonly?=?{
          ??readonly?[P?in?keyof?T]:?T[P]
          }

          //?選擇對(duì)象中的某些屬性
          type?Pickextends?keyof?T>?=?{
          ??[P?in?K]:?T[P];
          }

          //?......

          typescript 2.8 在 lib.d.ts 中內(nèi)置了幾個(gè)映射類(lèi)型:

          • Partial -- 將 T 中的所有屬性變成可選。
          • Readonly -- 將 T 中的所有屬性變成只讀。
          • Pick -- 選擇 T 中可以賦值給U的類(lèi)型。
          • Exclude -- 從T中剔除可以賦值給U的類(lèi)型。
          • Extract -- 提取T中可以賦值給U的類(lèi)型。
          • NonNullable -- 從T中剔除nullundefined
          • ReturnType -- 獲取函數(shù)返回值類(lèi)型。
          • InstanceType -- 獲取構(gòu)造函數(shù)類(lèi)型的實(shí)例類(lèi)型。

          所以我們平時(shí)寫(xiě) TS 時(shí)可以直接使用這些類(lèi)型工具:

          interface?ApiRes?{
          ??code:?string;
          ??flag:?string;
          ??message:?string;
          ??data:?object;
          ??success:?boolean;
          ??error:?boolean;
          }

          type?IApiRes?=?Pick'code'?|?'flag'?|?'message'?|?'data'>

          //?{
          //???code:?string;
          //???flag:?string;
          //???message:?string;
          //???data:?object;
          //?}

          extends 條件分發(fā)

          對(duì)于 T extends U ? X : Y 來(lái)說(shuō),還存在一個(gè)特性,當(dāng) T 是一個(gè)聯(lián)合類(lèi)型時(shí),會(huì)進(jìn)行條件分發(fā)。

          type?Union?=?string?|?number
          type?isNumber?=?T?extends?number???'isNumber'?:?'notNumber'

          type?UnionType?=?isNumber?//?'notNumber'?|?'isNumber'

          實(shí)際上,extends 運(yùn)算會(huì)變成如下形式:

          (string?extends?number???'isNumber'?:?'notNumber')?|?(number?extends?number???'isNumber'?:?'notNumber')

          Extract 就是基于此特性,再配合 never 幺元的特性實(shí)現(xiàn)的:

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

          type?T1?=?Exclude<string?|?number?|?boolean,?string?|?boolean>??//?number

          infer 關(guān)鍵詞

          infer 可以對(duì)運(yùn)算過(guò)程中的類(lèi)型進(jìn)行存儲(chǔ),內(nèi)置的ReturnType 就是基于此特性實(shí)現(xiàn)的:

          type?ReturnType?=?
          ??T?extends?(...args:?any)?=>?infer?R???R?:?never

          type?Fn?=?(str:?string)?=>?number

          type?FnReturn?=?ReturnType?//?number

          模塊

          全局模塊 vs. 文件模塊

          默認(rèn)情況下,我們所寫(xiě)的代碼是位于全局模塊下的:

          const?foo?=?2

          此時(shí),如果我們創(chuàng)建了另一個(gè)文件,并寫(xiě)下如下代碼,ts 認(rèn)為是正常的:

          const?bar?=?foo?//?ok

          如果要打破這種限制,只要文件中有 import 或者 export 表達(dá)式即可:

          export?const?bar?=?foo?//?error

          模塊解析策略

          Tpescript 有兩種模塊的解析策略:Node 和 Classic。當(dāng) tsconfig.json 中 module 設(shè)置成 AMD、System、ES2015 時(shí),默認(rèn)為 classic ,否則為 Node ,也可以使用 moduleResolution? 手動(dòng)指定模塊解析策略。

          兩種模塊解析策略的區(qū)別在于,對(duì)于下面模塊引入來(lái)說(shuō):

          import?moduleB?from?'moduleB'

          Classic 模式的路徑尋址:

          /root/src/folder/moduleB.ts
          /root/src/folder/moduleB.d.ts
          /root/src/moduleB.ts
          /root/src/moduleB.d.ts
          /root/moduleB.ts
          /root/moduleB.d.ts
          /moduleB.ts
          /moduleB.d.ts

          Node 模式的路徑尋址:

          /root/src/node_modules/moduleB.ts
          /root/src/node_modules/moduleB.tsx
          /root/src/node_modules/moduleB.d.ts
          /root/src/node_modules/moduleB/package.json?(如果指定了"types"屬性)
          /root/src/node_modules/moduleB/index.ts
          /root/src/node_modules/moduleB/index.tsx
          /root/src/node_modules/moduleB/index.d.ts

          /root/node_modules/moduleB.ts
          /root/node_modules/moduleB.tsx
          /root/node_modules/moduleB.d.ts
          /root/node_modules/moduleB/package.json?(如果指定了"types"屬性)
          /root/node_modules/moduleB/index.ts
          /root/node_modules/moduleB/index.tsx
          /root/node_modules/moduleB/index.d.ts

          /node_modules/moduleB.ts
          /node_modules/moduleB.tsx
          /node_modules/moduleB.d.ts
          /node_modules/moduleB/package.json?(如果指定了"types"屬性)
          /node_modules/moduleB/index.ts
          /node_modules/moduleB/index.tsx
          /node_modules/moduleB/index.d.ts

          聲明文件

          什么是聲明文件

          聲明文件已 .d.ts 結(jié)尾,用來(lái)描述代碼結(jié)構(gòu),一般用來(lái)為 js 庫(kù)提供類(lèi)型定義。

          平時(shí)開(kāi)發(fā)的時(shí)候有沒(méi)有這種經(jīng)歷:當(dāng)用npm安裝了某些包并使用的時(shí)候,會(huì)出現(xiàn)這個(gè)包的語(yǔ)法提示,下面是 vue 的提示:

          這個(gè)語(yǔ)法提示就是聲明文件的功勞了,先來(lái)看一個(gè)簡(jiǎn)單的聲明文件長(zhǎng)啥樣,這是jsonp這個(gè)庫(kù)的聲明文件:

          type?CancelFn?=?()?=>?void;
          type?RequestCallback?=?(error:?Error?|?null,?data:?any)?=>?void;

          interface?Options?{
          ????param?:?string;
          ????prefix?:?string;
          ????name?:?string;
          ????timeout?:?number;
          }

          declare?function?jsonp(url:?string,?options?:?Options,?cb?:?RequestCallback):?CancelFn;
          declare?function?jsonp(url:?string,?callback?:?RequestCallback):?CancelFn;

          export?=?jsonp;

          有了這份聲明文件,編輯器在使用這個(gè)庫(kù)的時(shí)候就可以根據(jù)這份聲明文件來(lái)做出相應(yīng)的語(yǔ)法提示。

          編輯器是怎么找到這個(gè)聲明文件?

          • 如果這個(gè)包的根目錄下有一個(gè)index.d.ts,那么這就是這個(gè)庫(kù)的聲明文件了。
          • 如果這個(gè)包的package.json中有types或者typings字段,那個(gè)該字段指向的就是這個(gè)包的聲明文件。

          上述兩種都是將聲明文件寫(xiě)在包里面的情況,如果某個(gè)庫(kù)很長(zhǎng)時(shí)間不維護(hù)了,或者作者消失了該怎么辦,沒(méi)關(guān)系,typescript官方提供了一個(gè)聲明文件倉(cāng)庫(kù),嘗試使用@types前綴來(lái)安裝某個(gè)庫(kù)的聲明文件:

          npm?i?@types/lodash

          當(dāng)引入lodash的時(shí)候,編輯器也會(huì)嘗試查找node_modules/@types/lodash 來(lái)為你提供lodash的語(yǔ)法提示。

          還有一種就是自己寫(xiě)聲明文件,編輯器會(huì)收集項(xiàng)目本地的聲明文件,如果某個(gè)包沒(méi)有聲明文件,你又想要語(yǔ)法提示,就可以自己在本地寫(xiě)個(gè)聲明文件:

          //?types/lodash.d.ts
          declare?module?"lodash"?{
          ??export?function?chunk(array:?any[],?size?:?number):?any[];
          ??export?function?get(source:?any,?path:?string,?defaultValue?:?any):?any;
          }

          如果源代碼是用ts寫(xiě)的,在編譯成js的時(shí)候,只要加上-d 參數(shù),就能生成對(duì)應(yīng)的聲明文件。

          tsc?-d

          聲明文件該怎么寫(xiě)可以參考https://www.tslang.cn/docs/handbook/declaration-files/introduction.html

          還要注意的是,如果某個(gè)庫(kù)有聲明文件了,編輯器就不會(huì)再關(guān)心這個(gè)庫(kù)具體的代碼了,它只會(huì)根據(jù)聲明文件來(lái)做提示。

          擴(kuò)展原生對(duì)象

          可能寫(xiě)過(guò) ts 的小伙伴有這樣的疑惑,我該如何在 window 對(duì)象上自定義屬性呢?

          window.myprop?=?1?//?error

          默認(rèn)的,window 上是不存在 myprop 這個(gè)屬性的,所以不可以直接賦值,當(dāng)然,可以使用方括號(hào)賦值語(yǔ)句,但是 get 操作時(shí)也必須用 [] ,并且沒(méi)有類(lèi)型提示。

          window['myprop']?=?1?//?OK

          window.myprop??//?類(lèi)型“Window?&?typeof?globalThis”上不存在屬性“myprop”
          window['myprop']?//?ok,但是沒(méi)有提示,沒(méi)有類(lèi)型

          此時(shí)可以使用聲明文件擴(kuò)展其他對(duì)象,在項(xiàng)目中隨便建一個(gè)xxx.d.ts

          //?index.d.ts
          interface?Window?{
          ??myprop:?number
          }

          //?index.ts
          window.myprop?=?2??//?ok

          也可以在模塊內(nèi)部擴(kuò)展全局對(duì)象:

          import?A?from?'moduleA'

          window.myprop?=?2

          declare?global?{
          ??interface?Window?{
          ????myprop:?number
          ??}
          }

          擴(kuò)展其他模塊

          如果使用過(guò) ts 寫(xiě)過(guò) vue ?的同學(xué),一定都碰到過(guò)這個(gè)問(wèn)題,如何擴(kuò)展 vue.prototype 上的屬性或者方法?

          import?Vue?from?'vue'

          Vue.prototype.myprops?=?1

          const?vm?=?new?Vue({
          ??el:?'#app'
          })

          //?類(lèi)型“CombinedVueInstance>”
          //?上不存在屬性“myprops”
          console.log(vm.myprops)

          vue 給出的方案,在項(xiàng)目中的 xxx.d.ts 中擴(kuò)展 vue 實(shí)例上的屬性:

          import?Vue?from?'vue'

          declare?module?'vue/types/vue'?{
          ??interface?Vue?{
          ????myprop:?number
          ??}
          }

          ts 提供了 declare module 'xxx' 的語(yǔ)法來(lái)擴(kuò)展其他模塊,這非常有利于一些插件化的庫(kù)和包,例如 vue-router 擴(kuò)展 vue

          //?vue-router/types/vue.d.ts
          import?Vue?from?'vue'
          import?VueRouter,?{?Route,?RawLocation,?NavigationGuard?}?from?'./index'

          declare?module?'vue/types/vue'?{
          ??interface?Vue?{
          ????$router:?VueRouter
          ????$route:?Route
          ??}
          }

          declare?module?'vue/types/options'?{
          ??interface?ComponentOptionsextends?Vue>?{
          ????router?:?VueRouter
          ????beforeRouteEnter?:?NavigationGuard
          ????beforeRouteLeave?:?NavigationGuard
          ????beforeRouteUpdate?:?NavigationGuard
          ??}
          }

          如何處理非 js 文件,例如 .vue 文件引入?

          處理 vue 文件

          對(duì)于所有以 .vue 結(jié)尾的文件,可以默認(rèn)導(dǎo)出 Vue 類(lèi)型,這是符合 vue單文件組件 的規(guī)則的。

          declare?module?'*.vue'?{
          ??import?Vue?from?'vue'
          ??export?default?Vue
          }

          處理 css in js

          對(duì)于所有的 .css,可以默認(rèn)導(dǎo)出一個(gè) any 類(lèi)型的值,這樣可以解決報(bào)錯(cuò)問(wèn)題,但是丟失了類(lèi)型檢查。

          declare?module?'*.css'?{
          ?const?content:?any
          ??export?default?content
          }
          import?*?as?React?from?'react'
          import?*?as?styles?from?'./index.css'

          const?Error?=?()?=>?(
          ????
          ?????????</div>
          ????????Ooooops!p>
          ????????

          This?page?doesn't?exist?anymore.


          ????

          )

          export?default?Error

          其實(shí)不管是全局?jǐn)U展還是模塊擴(kuò)展,其實(shí)都是基于 TS 聲明合并 的特性,簡(jiǎn)單來(lái)說(shuō),TS 會(huì)將它收集到的一些同名的接口、類(lèi)、類(lèi)型別名按照一定的規(guī)則進(jìn)行合并。

          編譯

          ts 內(nèi)置了一個(gè) compiler (tsc),可以讓我們把 ts 文件編譯成 js 文件,配合眾多的編譯選項(xiàng),有時(shí)候不需要 babel? 我們就可以完成大多數(shù)工作。

          常用的編譯選項(xiàng)

          tsc 在編譯 ts 代碼的時(shí)候,會(huì)根據(jù) tsconfig.json 配置文件的選項(xiàng)采取不同的編譯策略。下面是三個(gè)常用的配置項(xiàng):

          • target - 生成的代碼的JS語(yǔ)言的版本,比如ES3、ES5、ES2015等。
          • module - 生成的代碼所需要支持的模塊系統(tǒng),比如 es2015、commonjs、umd等。
          • lib - 告訴TS目標(biāo)環(huán)境中有哪些特性,比如 WebWorker、ES2015、DOM等。

          babel 一樣,ts 在編譯的時(shí)候只會(huì)轉(zhuǎn)化新 語(yǔ)法,不會(huì)轉(zhuǎn)化新的 API, 所以有些場(chǎng)景下需要自行處理 polyfill 的問(wèn)題。

          更改編譯后的目錄

          tsconfig 中的 outDir 字段可以配置編譯后的文件目錄,有利于 dist 的統(tǒng)一管理。

          {
          ??"compilerOptions":?{
          ????"module":?"umd",
          ????"outDir":?"./dist"
          ??}
          }

          編譯后的目錄結(jié)構(gòu):

          myproject
          ├──?dist
          │???├──?index.js
          │???└──?lib
          │???????└──?moduleA.js
          ├──?index.ts
          ├──?lib
          │???└──?moduleA.ts
          └──?tsconfig.json

          編譯后輸出到一個(gè)js文件中

          對(duì)于 amdsystem 模塊,可以配置 tsconfig.json 中的 outFile 字段,輸出為一個(gè) js 文件。
          如果需要輸出成其他模塊,例如 umd ,又希望打包成一個(gè)單獨(dú)的文件,需要怎么做?
          可以使用 rollup 或者 webpack

          //?rollup.config.js
          const?typescript?=?require('rollup-plugin-typescript2')

          module.exports?=?{
          ??input:?'./index.ts',
          ??output:?{
          ????name:?'MyBundle',
          ????file:?'./dist/bundle.js',
          ????format:?'umd'
          ??},
          ??plugins:?[
          ????typescript()
          ??]
          }

          一些常用的 ts 周邊庫(kù)

          • @typescript-eslint/eslint-plugin、@typescript-eslint/parser - lint 套件
          • DefinitelyTyped - @types 倉(cāng)庫(kù)
          • ts-loader、rollup-plugin-typescript2 - rollup、webpack 插件
          • typedoc - ts 項(xiàng)目自動(dòng)生成 API 文檔
          • typeorm - 一個(gè) ts 支持度非常高的、易用的數(shù)據(jù)庫(kù) orm 庫(kù)
          • nest.js、egg.js - 支持 ts 的服務(wù)端框架
          • ts-node - node 端直接運(yùn)行 ts 文件
          • utility-types - 一些實(shí)用的 ts 類(lèi)型工具
          • type-coverage - 靜態(tài)類(lèi)型覆蓋率檢測(cè)

          一個(gè)提高開(kāi)發(fā)效率的小技巧

          大家在日常開(kāi)發(fā)的時(shí)候,可能會(huì)經(jīng)常用到webpack的路徑別名,比如: import xxx from '@/path/to/name',如果編輯器不做任何配置的話(huà),這樣寫(xiě)會(huì)很尷尬,編譯器不會(huì)給你任何路徑提示,更不會(huì)給你語(yǔ)法提示。這里有個(gè)小技巧,基于 tsconfig.jsonbaseUrlpaths這兩個(gè)字段,配置好這兩個(gè)字段后,.ts文件里不但有了路徑提示,還會(huì)跟蹤到該路徑進(jìn)行語(yǔ)法提示。

          這里有個(gè)小彩蛋,可以把 tsconfig.json 重命名成jsconfig.json.js文件里也能享受到路徑別名提示和語(yǔ)法提示了。

          使用 webstorm 的同學(xué)如果也想使用的話(huà),只要打開(kāi)設(shè)置,搜索webpack,然后設(shè)置一下webpack配置文件的路徑就好了。

          學(xué)習(xí)推薦

          • Typescript 中文網(wǎng)
          • Typescript 入門(mén)教程
          • github - awesome-typescript
          • 知乎專(zhuān)欄 - 來(lái)玩TypeScript啊,機(jī)都給你開(kāi)好了!
          • conditional-types-in-typescript (ts 中的條件類(lèi)型)

          最后



          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 106
          點(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 | 黄片无遮挡 | 亚洲精品v |