1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        你不知道的 TypeScript 高級(jí)技巧

        共 17106字,需瀏覽 35分鐘

         ·

        2020-09-01 16:38

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

        前言

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

        Typescript 簡(jiǎn)介

        • ECMAScript 的超集 (stage 3)
        • 編譯期的類型檢查
        • 不引入額外開銷(零依賴,不擴(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ò)誤,寫出的代碼更加安全,減少 BUG
        • 享受到代碼提示帶來(lái)的好處
        • 重構(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,對(duì)于未知類型可以用 unknown。

        unknown 的正確用法

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

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

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

        never

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

        //?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ù)類型,用來(lái)描述一個(gè)函數(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òng)推導(dǎo)參數(shù)類型
        let?myAdd:?FnType?=?function(x,?y)?{
        ??return?x?+?y
        }

        函數(shù)重載?

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

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

        //?重載簽名(函數(shù)類型定義)
        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ì)外不可見

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

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

        //?重載簽名(函數(shù)類型定義)
        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)合類型
        //?即?string?|?number
        const?input?=?stringOrNumber(1)

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

        類型推斷

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

        基本類型推斷:

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

        對(duì)象類型推斷:

        // 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)?{}

        所以有時(shí)候?qū)τ谝恍┖?jiǎn)單的類型可以不用手動(dòng)聲明其類型,讓 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

        java、c++ 等傳統(tǒng)靜態(tài)類型語(yǔ)言是基于 名義子類型 的,必須顯示聲明子類型關(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ì)象子類型

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

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

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

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

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

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

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

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

        • 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)說,WangCai 是 Dog 的子類,Dog 類上沒有 dance 方法, 產(chǎn)生異常。
        //?對(duì)于出參來(lái)說,WangCai 類繼承了 Dog 類,肯定會(huì)有 bark 方法
        getDogName((wangcai:?WangCai)?=>?{
        ??wangcai.dance()
        ??return?new?WangCai()
        })

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

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

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

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

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

        其實(shí)函數(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 ,即長(zhǎng)度大的是長(zhǎng)度小的子類型,再由于函數(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í)類型

        聯(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 侵入了運(yùn)行時(shí)。

        類型保護(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 推斷出來(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 判斷出變量的基本類型:

        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. 針對(duì) null 和 undefined 的類型保護(hù)

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

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

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

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

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

        typeof 關(guān)鍵詞

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

        注意:此時(shí)的 typeof 是一個(gè)類型關(guān)鍵詞,只可以用在類型語(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è) 類型關(guān)鍵詞 ,可以用來(lái)取得一個(gè)對(duì)象接口的所有 key 值:

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

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

        in 關(guān)鍵詞

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

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

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

        [ ] 操作符

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

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

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

        一個(gè)小栗子

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

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

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

        type?Person1?=?Copy

        泛型

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

        小試牛刀

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

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

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

        泛型約束

        如果我們只希望 createList 函數(shù)只能生成指定的類型數(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ù)組類型,數(shù)組類型是有 length 屬性的,所以 ok。string 類型也是有 length 屬性的,所以 ok。但是 boolean 就不能通過這個(gè)約束了。

        條件控制

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

        表達(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)于一個(gè)類型的函數(shù),可以做一些類型運(yùn)算,輸入一個(gè)類型,輸出另一個(gè)類型,前文我們舉了個(gè) Copy 的例子。

        幾個(gè)內(nè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è)映射類型:

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

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

        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)說,還存在一個(gè)特性,當(dāng) T 是一個(gè)聯(lián)合類型時(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)算過程中的類型進(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)情況下,我們所寫的代碼是位于全局模塊下的:

        const?foo?=?2

        此時(shí),如果我們創(chuàng)建了另一個(gè)文件,并寫下如下代碼,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)說:

        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ù)提供類型定義。

        平時(shí)開發(fā)的時(shí)候有沒有這種經(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è)包的聲明文件。

        上述兩種都是將聲明文件寫在包里面的情況,如果某個(gè)庫(kù)很長(zhǎng)時(shí)間不維護(hù)了,或者作者消失了該怎么辦,沒關(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ǔ)法提示。

        還有一種就是自己寫聲明文件,編輯器會(huì)收集項(xiàng)目本地的聲明文件,如果某個(gè)包沒有聲明文件,你又想要語(yǔ)法提示,就可以自己在本地寫個(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寫的,在編譯成js的時(shí)候,只要加上-d 參數(shù),就能生成對(duì)應(yīng)的聲明文件。

        tsc?-d

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

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

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

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

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

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

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

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

        此時(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ò)展其他模塊

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

        import?Vue?from?'vue'

        Vue.prototype.myprops?=?1

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

        //?類型“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 類型,這是符合 vue單文件組件 的規(guī)則的。

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

        處理 css in js

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

        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)說,TS 會(huì)將它收集到的一些同名的接口、類、類型別名按照一定的規(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 的問題。

        更改編譯后的目錄

        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 類型工具
        • type-coverage - 靜態(tài)類型覆蓋率檢測(cè)

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

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

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

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

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

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


        分享前端好文,點(diǎn)亮?在看?

        瀏覽 30
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            天堂在线观看视频 | 国产精品成人自产拍在线观看 | 精品电影久久久 | 老师撩起裙子让我挺进去 | 女生扒开腿让男生插 | 泷泽萝拉av在线 浪潮av在线播放 | 草比视频网 | 成人爱爱小视频 | 欧美激情中文字幕 | 淫乱一级片|