<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 高級技巧

          共 17106字,需瀏覽 35分鐘

           ·

          2020-09-01 16:38

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

          前言

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

          Typescript 簡介

          • ECMAScript 的超集 (stage 3)
          • 編譯期的類型檢查
          • 不引入額外開銷(零依賴,不擴(kuò)展 js 語法,不侵入運行時)
          • 編譯出通用的、易讀的 js 代碼

          Typescript = Type + ECMAScript + Babel-Lite

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

          為什么使用 Typescript

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

          基礎(chǔ)類型

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

          anyunknown 的區(qū)別

          • any: 任意類型
          • unknown: 未知的類型

          任何類型都能分配給 unknown,但 unknown 不能分配給其他基本類型,而 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)于完全丟失了類型檢查,所以大家盡量少用 any,對于未知類型可以用 unknown

          unknown 的正確用法

          我們可以通過不同的方式將 unknown 類型縮小為更具體的類型范圍:

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

          這個過程叫類型收窄(type narrowing)。

          never

          never 一般表示哪些用戶無法達(dá)到的類型。在最新的 typescript 3.7 中,下面代碼會報錯:

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

          const?x?=?2

          neverReach()

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

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

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

          函數(shù)類型

          幾種函數(shù)類型的返回值類型寫法

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

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

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

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

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

          函數(shù)類型

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

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

          完整的函數(shù)寫法

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

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

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

          函數(shù)重載?

          js因為是動態(tài)類型,本身不需要支持重載,ts為了保證類型安全,支持了函數(shù)簽名的類型重載。即:

          多個重載簽名和一個實現(xiàn)簽名

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

          //?實現(xiàn)簽名(函數(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

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

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

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

          len(2)?//?error

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

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

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

          重載簽名的類型不會合并

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

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

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

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

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

          類型推斷

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

          基本類型推斷:

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

          對象類型推斷:

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

          函數(shù)類型推斷:

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

          上下文類型推斷:

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

          所以有時候?qū)τ谝恍┖唵蔚念愋涂梢圆挥檬謩勇暶髌漕愋停?ts 自己去推斷。

          類型兼容性

          typescript 的子類型是基于 結(jié)構(gòu)子類型 的,只要結(jié)構(gòu)可以兼容,就是子類型。(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)類型語言是基于 名義子類型 的,必須顯示聲明子類型關(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;
          ??}
          }

          對象子類型

          子類型中必須包含源類型所有的屬性和方法:

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

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

          getPointX(point)?//?OK

          注意: 如果直接傳入一個對象字面量是會報錯的:

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

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

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

          函數(shù)子類型

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

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

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

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

          WangCai???Dog???Animal?//?即旺財屬于狗屬于動物

          問題:以下哪種類型是 Dog => Dog 的子類呢?

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

          從代碼來看解答

          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()
          }

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

          //?對于入?yún)碚f,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產(chǎn)生異常。
          //?對于出參來說,Animal 類上沒有 bark 方法, 產(chǎn)生異常。
          getDogName((wangcai:?WangCai)?=>?{
          ??wangcai.dance()
          ??return?new?Animal()
          })

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

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

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

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

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

          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 ,即長度大的是長度小的子類型,再由于函數(shù)參數(shù)的逆變特性,所以函數(shù)參數(shù)少的可以賦值給參數(shù)多的(參數(shù)從前往后需一一對應(yīng)),從數(shù)組的 forEach 方法就可以看出來:

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

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

          高級類型

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

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

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

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

          交叉類型表示多種類型的 “與” 關(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)合類型表示枚舉

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

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

          可以避免使用 enum 侵入了運行時。

          類型保護(hù)

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

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

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

          如何讓 ts 推斷出來上下文的類型呢?

          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 中,代碼實現(xiàn)中的 typeof 關(guān)鍵詞能夠幫助 ts 判斷出變量的基本類型:

          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ù)的類型:

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

          4. 針對 null 和 undefined 的類型保護(hù)

          在條件判斷中,ts 會自動對 null 和 undefined 進(jìn)行類型保護(hù):

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

          5. 針對 null 和 undefined 的類型斷言

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

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

          typeof 關(guān)鍵詞

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

          注意:此時的 typeof 是一個類型關(guān)鍵詞,只可以用在類型語法中。

          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 也是一個 類型關(guān)鍵詞 ,可以用來取得一個對象接口的所有 key 值:

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

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

          in 關(guān)鍵詞

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

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

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

          [ ] 操作符

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

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

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

          一個小栗子

          寫一個類型復(fù)制的類型工具:

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

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

          type?Person1?=?Copy

          泛型

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

          小試牛刀

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

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

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

          泛型約束

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

          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[] 是一個數(shù)組類型,數(shù)組類型是有 length 屬性的,所以 ok。string 類型也是有 length 屬性的,所以 ok。但是 boolean 就不能通過這個約束了。

          條件控制

          extends 除了做約束類型,還可以做條件控制,相當(dāng)于與一個三元運算符,只不過是針對 類型 的。

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

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

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

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

          映射類型

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

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

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

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

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

          //?......

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

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

          所以我們平時寫 TS 時可以直接使用這些類型工具:

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

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

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

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

          實際上,extends 運算會變成如下形式:

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

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

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

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

          infer 關(guān)鍵詞

          infer 可以對運算過程中的類型進(jìn)行存儲,內(nèi)置的ReturnType 就是基于此特性實現(xiàn)的:

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

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

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

          模塊

          全局模塊 vs. 文件模塊

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

          const?foo?=?2

          此時,如果我們創(chuàng)建了另一個文件,并寫下如下代碼,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 時,默認(rèn)為 classic ,否則為 Node ,也可以使用 moduleResolution? 手動指定模塊解析策略。

          兩種模塊解析策略的區(qū)別在于,對于下面模塊引入來說:

          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é)尾,用來描述代碼結(jié)構(gòu),一般用來為 js 庫提供類型定義。

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

          這個語法提示就是聲明文件的功勞了,先來看一個簡單的聲明文件長啥樣,這是jsonp這個庫的聲明文件:

          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;

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

          編輯器是怎么找到這個聲明文件?

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

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

          npm?i?@types/lodash

          當(dāng)引入lodash的時候,編輯器也會嘗試查找node_modules/@types/lodash 來為你提供lodash的語法提示。

          還有一種就是自己寫聲明文件,編輯器會收集項目本地的聲明文件,如果某個包沒有聲明文件,你又想要語法提示,就可以自己在本地寫個聲明文件:

          //?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寫的,在編譯成js的時候,只要加上-d 參數(shù),就能生成對應(yīng)的聲明文件。

          tsc?-d

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

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

          擴(kuò)展原生對象

          可能寫過 ts 的小伙伴有這樣的疑惑,我該如何在 window 對象上自定義屬性呢?

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

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

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

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

          此時可以使用聲明文件擴(kuò)展其他對象,在項目中隨便建一個xxx.d.ts

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

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

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

          import?A?from?'moduleA'

          window.myprop?=?2

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

          擴(kuò)展其他模塊

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

          import?Vue?from?'vue'

          Vue.prototype.myprops?=?1

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

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

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

          import?Vue?from?'vue'

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

          ts 提供了 declare module 'xxx' 的語法來擴(kuò)展其他模塊,這非常有利于一些插件化的庫和包,例如 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 文件

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

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

          處理 css in js

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

          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

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

          編譯

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

          常用的編譯選項

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

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

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

          更改編譯后的目錄

          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

          編譯后輸出到一個js文件中

          對于 amdsystem 模塊,可以配置 tsconfig.json 中的 outFile 字段,輸出為一個 js 文件。
          如果需要輸出成其他模塊,例如 umd ,又希望打包成一個單獨的文件,需要怎么做?
          可以使用 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 周邊庫

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

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

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

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

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

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

          • Typescript 中文網(wǎng)
          • Typescript 入門教程
          • github - awesome-typescript
          • 知乎專欄 - 來玩TypeScript啊,機都給你開好了!
          • conditional-types-in-typescript (ts 中的條件類型)


          分享前端好文,點亮?在看?

          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产精品国产成人国产三级 | 99久久免费观看视频 | 大香蕉大香蕉75 | 校园春色亚洲App下载 | 亚洲无码在|