1. 你應(yīng)該知道的TypeScript高級概念

        共 13207字,需瀏覽 27分鐘

         ·

        2020-12-06 10:01


        來源 |?https://juejin.cn/post/6897779423858737166
        作者|隱冬

        接口

        例如我們這定義一個叫做printPost的函數(shù),那這個函數(shù)可以接收一個文章對象參數(shù)post,然后在函數(shù)的內(nèi)部去打印文章的title, 然后再去打印他的content屬性。
        function printPost (post) {    console.log(post.title);    console.log(post.content);}
        那這個時候?qū)τ谶@個函數(shù)所接收的post對象他就有一定的要求,也就是我們所傳入的這個對象必須要存在一個title屬性和一個content屬性,只不過這種要求他實際上是隱性的,他沒有明確的表達出來。
        那這種情況下我們就可以使用接口去表現(xiàn)出來這種約束,這里我們可以嘗試先去定義一個接口。
        定義接口的方式呢就是使用interface這樣一個關(guān)鍵詞,然后后面跟上接口的名稱,這里我們可以叫做post,然后就是一對{},然后{}里面就可以添加具體的成員限制。這里我們添加一個title和content,類型都是string。
        interface Post {    title: string;    content: string;}
        注意這里我們可以使用逗號分割成員,但是更標準的做法是使用分號去分割,而且呢這個分號跟js中絕大多數(shù)的分號是一樣的,可以省略,那關(guān)于是否應(yīng)該在代碼當中明確使用每一個分號,個人的編碼習(xí)慣是不加,你可以根據(jù)你所在的團隊或者是項目對應(yīng)的編碼規(guī)范來去決定要不要加分號,這個問題我們不做過多討論。
        完成過后我們這里可以給這個post參數(shù)的類型設(shè)置為我們剛剛所定義的Post接口。
        function printPost (post: Post) {    console.log(post.title);    console.log(post.content);}
        printPost({ title: 'hello', content: 'typescript'})
        那此時就是顯示的要求我們所傳入的對象他必須要有title和content這兩個成員了,那這就是接口的一個基本作用。
        一句話去總結(jié),接口就是用來約束對象的結(jié)構(gòu),那一個對象去實現(xiàn)一個接口,他就必須要去擁有這個接口當中所約束的所有的成員。
        我們可以編譯一下這個代碼,編譯過后我們打開對應(yīng)的js文件,我們在js當中并不會發(fā)現(xiàn)有任何跟接口相關(guān)的代碼,也就是說TypeScript中的接口他只是用來為我們有結(jié)構(gòu)的數(shù)據(jù)去做類型約束的,在實際運行階段呢,實際這種接口他并沒有意義。
        可選成員,只讀成員
        對于接口中約定的成員,還有一些特殊的用法,我們依次來看一下。
        首先是可選成員,如果說我們在一個對象當中,我們某一個成員他是可有可無的話,那這樣的話我們對于約束這個對象的接口來說我們可以使用可選成員這樣一個特性。
        例如我們這里添加一個subTitle這樣一個成員,他的類型同樣是string,不過我們這里的文章不一定是每一個都有subTitle,這種況下我們就可以在subTitle后面添加一個問號,這就表示我們這個subTitle成員他是可有可無的了。
        interface Post {    title: string;    content: string;    subTitle?: string;}
        那這種用法呢其實就是相當于給這個subTitle標記他的類型是string或者是undefined。這就是可選成員。
        接下來我們再來看一下只讀成員這樣一個特性,那這里我們再給Post接口添加一個summary這樣一個成員,那一般邏輯上來講的話文章的summary他都是從文章的內(nèi)容當中自動提取出來的,所以說我們不應(yīng)該允許外界去設(shè)置他。
        那這種情況下我們可以使用readonly這樣一個關(guān)鍵詞,去修飾一下這里的summary。那添加了readonly過后我們這個summary他在初始化完成過后就不能夠再去修改了。如果我們再去修改就會報錯。這就是只讀成員。
        interface Post {    title: string;    content: string;    subTitle?: string;    readonly summary: string;}
        最后我們再來看一個動態(tài)成員的用法,那這種用法一般是適用于一些具有動態(tài)成員對象,例如程序當中的緩存對象,那他在運行過程中就會出現(xiàn)一些動態(tài)的鍵值。
        這里我們來新建一個新的接口,因為我們在定義的時候我們是無法知道會有那些具體的成員,所以說我們就不能夠去指定,具體的成員名稱,而是使用一個[], 這個[]中使用key: string。
        這個key并不是固定的,可以是任意的名稱, 只是代表了我們屬性的名稱,他是一個格式,然后后面這個string就是成員名的類型,也就是鍵的類型,后面我們可以跟上動態(tài)屬性的值為string。
        interface Cache {    [key: string]: string;}
        完成以后我們再來創(chuàng)建一個cache對象,讓他去實現(xiàn)這個接口,那這個時候我們就可以在這個cache對象上動態(tài)的去添加任意的成員了, 只不過這些成員他都必須是stringd類型的鍵值。
        const cache: Cache = {};
        cache.foo = 'value1';cache.bar = 'value2'

        類的基本使用

        類可以說是面向?qū)ο缶幊讨幸粋€最重要的概念,關(guān)于類的作用這里我們再簡單描述一下。他就是用來描述一類具體事物的抽象特征,我們可以以生活角度去舉例。
        例如手機就屬于一個類型,那這個類型的特征呢就是能夠打電話,發(fā)信息。那在這個類型下面呢他還會有一些細分的子類,那這種子類他一定會滿足父類的所有特征,然后再多出來一些額外的特征。例如只能手機,他除了可以打電話發(fā)短信還能夠使用一些app。
        那我們是不能直接去使用類的,而是去使用這個類的具體事物,例如你手中的只能手機。
        那類比到程序的角度,類也是一樣的,他可以用來去描述一類具體對象的一些抽象成員,那在ES6以前,JavaScript都是通過函數(shù)然后配合原型去模擬實現(xiàn)的類。那從ES6開始,JavaScript中有了專門的class。
        而在TypeScript中,我們除了可以使用所有ECMAScript的標準當中所有類的功能,他還添加了一些額外的功能和用法,例如我們對類成員有特殊的訪問修飾符,還有一些抽象類的概念。
        那對于ECMAScript標準中的class,我們這里就不單獨去介紹了,如果你不太熟悉,你可以去參考ECMAScript前面的文章。
        這里我們來著重介紹一下,在TypeScript中額外多出來的一些新的類的一些特性,我們可以先來聲明一個叫做Person的類型,然后我們在這個類型當中去聲明一下constructor構(gòu)造桉樹,在構(gòu)造函數(shù)當中我們接收一個name和age參數(shù),那這里我們?nèi)匀豢梢允褂妙愋妥⒔獾姆绞饺俗⑽覀冞@個地方每個參數(shù)的類型。
        然后在這個構(gòu)造函數(shù)的里面我們可以使用this去為當前這個類型的屬性去賦值,不過這里直接去使用this訪問當前類的屬性會報錯,說的是當前這個Person類型上面并不存在對應(yīng)的name和age。
        class Person {    constructor(name: string, age: number) {        this.name = name;        this.age = age;    }}
        這是因為在TypeScript中我們需要明確在類型中取聲明他所擁有的一些屬性,而不是直接在構(gòu)造函數(shù)當中動態(tài)通過this去添加。
        那在類型聲明屬性的方式就是直接在類當中去定義, 那這個語法呢是ECMAScript2016標準當中定義的,那我們同樣可以在這里給name和gae屬性添加類型。
        那他也可以通過等號去直接賦值一個初始值,不過一般情況下我們還是會在構(gòu)造函數(shù)中動態(tài)的為屬性賦值。
        需要注意的是,在TypeScript中類的屬性他必須要有一個初始值,可以在等號后面去賦值,或者是在構(gòu)造函數(shù)當中去初始化,兩者必須做其一,否則就會報錯。
        class Person {    name: string = 'init name';    age: number;    constructor(name: string, age: number) {        this.name = name;        this.age = age;    }}
        那以上就是在TypeScript中類屬性在定義上的一些細微差異,其實具體來說就是我們類的屬性他在使用之前必須要現(xiàn)在類型當中去聲明,那這么做的目的其實就是為了給我們的屬性去做一些類型的標注。
        那除此之外呢我們?nèi)匀豢梢园凑誆S6標準當中的語法,為這個類型去聲明一些方法,例如我們這里添加一個叫做sayHi的方法,那在這個方法當中我們?nèi)匀豢梢允褂煤瘮?shù)類型注解的方式去限制參數(shù)的類型和返回值的類型。
        那在這個方法的內(nèi)部呢我們同樣可以使用this去訪問當前實例對象,也就可以訪問到對應(yīng)的屬性。
        class Person {    name: string = 'init name';    age: number;    constructor(name: string, age: number) {        this.name = name;        this.age = age;    }
        sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
        那以上就是類在TypeScript中的一個基本使用。

        類的訪問修飾符

        接下來我們再來看幾個TypeScript中類的一些特殊用法,那首先就是類當中成員的訪問修飾符,類中的每一個成員都可以使用訪問修飾符去修飾他們。
        例如我們這里給age屬性前面去添加一個private,表示這個age屬性是一個私有屬性,這種私有屬性只能夠在類的內(nèi)部去訪問,這里我們創(chuàng)建一個Person對象, 我們打印tom的name屬性和age屬性??梢园l(fā)現(xiàn)name可以訪問,age就會報錯,因為age已經(jīng)被我們標記為了私有屬性。
        class Person {    name: string = 'init name';    private age: number;    constructor(name: string, age: number) {        this.name = name;        this.age = age;    }
        sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
        const tom = new Person('tom', 18);
        console.log(tom.name);console.log(tom.age);
        除了private以外,我們還可以使用public修飾符去修飾成員,意思是他是一個共有成員,不過再TypeScript中,類成員的訪問修飾符默認就是public,所以我們這里加不加public效果都是一樣的。
        不過我們還是建議大家手動去加上這種public的修飾符,因為這樣的話,我們的代碼會更加容易理解一點。
        class Person {    public name: string = 'init name';    private age: number;    constructor(name: string, age: number) {        this.name = name;        this.age = age;    }
        sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
        最后還有一個叫做protected修飾符,說的就是受保護的,我們可以添加一個gender的屬性,他的訪問修飾符我們就使用protected,我們同樣在構(gòu)造函數(shù)中初始化一下gender。
        完成過后我們在實例對象上去訪問gender,會發(fā)現(xiàn)也是訪問不到的,也就是protected也不能在外部直接訪問。
        class Person {    public name: string = 'init name';    private age: number;    protected gender: boolean;    constructor(name: string, age: number) {        this.name = name;        this.age = age;        this.gender = true;    }
        sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
        const tom = new Person('tom', 18);
        console.log(tom.name);// console.log(tom.age);console.log(tom.gender);
        那他跟private的區(qū)別到底在什么地方呢, 這里我們可以再定義一個叫做Student的類型,我們讓這個類型去繼承自Person,我們在構(gòu)造函數(shù)中嘗試訪問父類的gender,是可以訪問到的。那意思就是protected只允許在子類當中去訪問對應(yīng)的成員。
        class Student extends Person {    constructor(name: string, age: number) {        super(name, age); // 子類需要調(diào)用super將參數(shù)傳給父類。        console.log(this.gender);    }}
        那以上就是TypeScript當中對于類額外添加的三個訪問修飾符。分別是private,protected和public,那他們的作用可以用來去控制類當中的成員的可訪問級別。
        那這里還有一個需要注意的點,就是對于構(gòu)造函數(shù)的訪問修飾符,那構(gòu)造函數(shù)的訪問修飾符默認也是public,那如果說我們把它設(shè)置為private,那這個類型就不能夠在外部被實例化了,也不能夠被繼承,那在這樣一種情況下,我們就只能夠在這個類的內(nèi)部去添加一個靜態(tài)方法,然后在靜態(tài)方法當中去創(chuàng)建這個類型的實例,因為private只允許在內(nèi)部訪問。
        例如我們這里再去添加一個叫create的靜態(tài)方法,那static也是ES6標準當中定義的,然后我們就可以在這個create方法中去使用new的方式去創(chuàng)建這個類型的實例,因為new的方式就是調(diào)用了這個類型的構(gòu)造函數(shù)。
        此時我們就可以在外部去使用create靜態(tài)方法創(chuàng)建Student類型的對象了。
        class Student extends Person {    private constructor(name: string, age: number) {        super(name, age); // 子類需要調(diào)用super將參數(shù)傳給父類。        console.log(this.gender);    }
        static create(name: string, age: number) { return new Student(name, age); }}
        const jack = Student.create('jack', 18);
        那如果我們把構(gòu)造函數(shù)標記為protected,這樣一個類型也是不能在外面被實例化的,但是相比于private他是允許繼承的,這里我們就不單獨演示了。

        類的只讀屬性

        對屬性成員我們除了可以使用private和protected去控制它的訪問級別,我們還可以使用一個叫做readonly的關(guān)鍵詞去把這個成員設(shè)置為只讀的。
        這里我們將gender屬性設(shè)置為readonly,注意這里如果說我們的屬性已經(jīng)有了訪問修飾符的話,那readonly應(yīng)該跟在訪問修飾符的后面,對于只讀屬性,我們可以選擇在類型聲明的時候直接通過等號的方式去初始化,也可以在構(gòu)造函數(shù)當中去初始化,二者只能選其一。
        也就是說我們不能在聲明的時候初始化,然后在構(gòu)造函數(shù)中修改它,因為這樣的話已經(jīng)破壞了readonly。
        class Person {    public name: string = 'init name';    private age: number;    protected readonly gender: boolean;    constructor(name: string, age: number) {        this.name = name;        this.age = age;        this.gender = true;    }
        sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); console.log(this.age); }}
        const tom = new Person('tom', 18);
        console.log(tom.name);
        在初始化過后呢, 這個gender屬性就不允許再被修改了,無論是在內(nèi)部還是外部,他都是不允許修改的。
        以上就是readonly這樣一個只讀屬性,還是比較好理解的。

        類與接口

        相比于類,接口的概念要更為抽象一點,我們可以接著之前所說的手機的例子來去做比。我們說手機他是一個類型,這個實例的類型都是能夠打電話,發(fā)短信的,因為手機這個類的特征呢就是打電話,發(fā)短信。
        但是呢,我們能夠打電話的,不僅僅只有手機,在以前還有比較常見的座機,也能夠打電話,但是座機并不屬于手機這個類目,而是一個單獨的類目,因為他不能夠發(fā)短信,也不能夠拿著到處跑。
        那在這種情況下就會出現(xiàn),不同的類與類之間也會有一些共同的特征,那對于這些公共的特征我們一般會使用接口去抽象,那你可以理解為手機也可以打電話,因為他實現(xiàn)了能夠打電話的協(xié)議,而座機也能夠打電話因為他也實現(xiàn)了這個相同的協(xié)議。
        那這里所說的協(xié)議呢我們在程序當中就叫做接口,當然如果說你是第一次接受這種概念的話,那可能理解起來會有些吃力,個人的經(jīng)驗就是多思考,多從生活的角度去想,如果實在想不通,更粗暴的辦法就是不斷的去用,用的過程當中慢慢的去總結(jié)規(guī)律時間長了自然也就好了。
        class Person {    eat (food: string): void {}    run (distance: number) {}}
        class Animal { eat (food: string): void {} run (distance: number) {}}
        這里我們來看一個例子,我們定義好了兩個類型,分別是Person和Animal,也就是人類和動物類,那他們實際上是兩個完全不同的類型,但是他們之間也會有一些相同的特性。例如他們都會吃東西都會跑。
        那這種情況下就屬于不同的類型實現(xiàn)了一個相同的接口,那可能有人會問,我們?yōu)槭裁床唤o他們之間抽象一個公共的父類,然后把公共的方法都定義到父類當中。
        那這個原因也很簡單,雖然人和動物都會吃,都會跑,但是我們說人吃東西和狗吃東西能是一樣的么,那他們只是都有這樣的能力,而這個能力的實現(xiàn)肯定是不一樣的。
        那在這種情況下我們就可以使用接口去約束這兩個類型之間公共的能力,我們定義一個接口叫做EatAndRun,然后我們在這個接口當中分別去添加eat和run這兩個方法成員的約束。
        那這里我們就需要使用這種函數(shù)簽名的方式去約束這兩個方法的類型,而我們這里是不做具體的方法實現(xiàn)的。
        interface EatAndRun {    eat (food: string): void;    run (distance: number): void;}
        那有了這個接口過后呢,我們再到Person類型后面,我們使用implements實現(xiàn)以下這個EatAndRun接口, 那此時在我們這個類型中就必須要有對應(yīng)的成員,如果沒有就會報錯,因為我們實現(xiàn)這個接口就必須有他對應(yīng)的成員。
        class Person implements EatAndRun {    eat (food: string): void {}    run (distance: number) {}}
        那這里我們需要注意的一點是,在C#和Java這樣的一些語言當中他建議我們盡可能讓每一個接口的定義更加簡單,更加細化。就是我們EatAndRun這樣一個接口當中我們抽象了兩個方法,那就相當于抽象了兩個能力,那這兩個能力必然會同時存在么?是不一定的。
        例如說摩托車也會跑,但是就不會吃東西,所以說我們更為合理的就是一個接口只去約束一個能力,然后讓一個類型同時去實現(xiàn)多個接口,那這樣的話會更加合理一些,那我們這里可以把這個接口拆成一個Eat接口和Run接口,每個接口只有一個成員。
        然后我們就可以在類型的后面使用逗號的方式,同時去實現(xiàn)Eat和Run這兩個接口。
        interface Eat {    eat (food: string): void;}
        interface Run { run (distance: number): void;}
        class Person implements Eat, Run { eat (food: string): void {} run (distance: number) {}}
        那以上呢就是用接口去對類進行一些抽象,那這里再多說一句題外話,就是大家千萬不要把自己框死在某一門語言或者是技術(shù)上面,最好可以多接觸,多學(xué)習(xí)一些周邊的語言或者技術(shù),因為這樣的話可以補充你的知識體系。
        那最簡單來說,一個只了解JavaScript的開發(fā)人員,即便說他對JavaScript有多么精通,那他也不可能設(shè)計出一些比較高級的產(chǎn)品。
        例如我們現(xiàn)在比較主流的一些框架,他們大都采用一些MVVM的這樣一些思想,那這些思想呢他實際上最早出現(xiàn)在微軟的WPS技術(shù)當中的,如果說你有更寬的知識面的話,那你可以更好的把多家的思想融合到一起,所以說我們的視野應(yīng)該放寬一點。

        抽象類

        最后我們再來了解一下抽象類,那抽象類在某種程度上來說跟接口有點類似,那他也是用來約束子類當中必須要有某一個成員。
        但是不同于接口的是,抽象類他可以包含一些具體的實現(xiàn),而接口他只能夠是一個成員的一個抽象,他不包含具體的實現(xiàn)。
        那一般比較大的類目我們都建議大家使用抽象類,例如我們剛剛所說的動物類,那其實他就應(yīng)該是抽象的,因為我們所說的動物他只是一個泛指,他并不夠具體,那在他的下面一定會有一些更細化的劃分,比如說小狗,小貓之類的。
        而且我們在生活當中一般都會說我們買了一條狗,或者說買了一只貓,從來沒有人說我們買了一個動物。
        abstract class Animal {    eat (food: string): void {}}
        我們有一個Animal類型,那他應(yīng)該像我們剛剛說的那樣,被定義成抽象類,定義抽象類的方式就是在class關(guān)鍵詞前面添加一個abstract,那這個類型被定義成抽象類過后,他就只能夠被繼承,不能夠再去實例化。
        在這種情況下我們就必須使用子類去繼承這個抽象類, 這里我們定義一個叫做Dog的類型, 然后我們讓他繼承自Animal,那在抽象類當中我們還可以去定義一些抽象方法,那這種抽象方法我們可以使用abstract關(guān)鍵詞來修飾,我們這里定義一個叫做run的抽象方法, 需要注意的是抽象方法也不需要方法體.
        當父類中有抽象方法時,我們的子類就必須要去實現(xiàn)這個方法。
        那此時我們再去使用這個子類所創(chuàng)建的對象時,就會同時擁有父類當中的一些實例方法以及自身所實現(xiàn)的方法。那這就是抽象類的基本使用。
        abstract class Animal {    eat (food: string): void {}    abstract run (distance: number): void;}
        class Dog extends Animal { run (distance: number): void {}}
        關(guān)于抽象類呢,更多的還是去理解他的概念,他在使用上并沒有什么復(fù)雜的地方。

        泛型

        泛型(Generics)是指在定義函數(shù)、接口或者類的時候, 不預(yù)先指定其類型,而是在使用是手動指定其類型的一種特性。
        比如我們需要創(chuàng)建一個函數(shù), 這個函數(shù)會返回任何它傳入的值。
        function identity(arg: any): any {  return arg}
        identity(3) // 3
        這代代碼編譯不會出錯,但是存在一個顯而易見的缺陷, 就是沒有辦法約束輸出的類型與輸入的類型保持一致。
        這時,我們可以使用泛型來解決這個問題;
        function identity<T>(arg: T): T {  return arg}
        identity(3) // 3
        我們在函數(shù)名后面加了 , 其中的 T 表示任意輸入的類型, 后面的 T 即表示輸出的類型,且與輸入保持一致。
        當然我們也可以在調(diào)用時手動指定輸入與輸出的類型, 如上述函數(shù)指定 string 類型:
        identity<number>(3) // 3
        在泛型函數(shù)內(nèi)部使用類型變量時, 由于事先并不知道它是那種類型, 所以不能隨意操作它的屬性和方法:
        function loggingIdentity<T>(arg: T): T {  console.log(arg.length)   // err   return arg}
        上述函數(shù)中 類型 T 上不一定存在 length 屬性, 所以編譯的時候就報錯了。
        這時,我們可以的對泛型進行約束,對這個函數(shù)傳入的值約束必須包含 length 的屬性, 這就是泛型約束:
        interface lengthwise {  length: number}
        function loggingIdentity<T extends lengthwise>(arg: T): T { console.log(arg.length) // err return arg}
        loggingIdentity({a: 1, length: 1}) // 1loggingIdentity('str') // 3loggingIdentity(6) // err 傳入是參數(shù)中未能包含 length 屬性
        這樣我們就可以通過泛型約束的方法對函數(shù)傳入的參數(shù)進行約束限制。
        多個參數(shù)時也可以在泛型約束中使用類型參數(shù) 如你聲明了一個類型參數(shù), 它被另一類型參數(shù)所約束?,F(xiàn)在想要用屬性名從對象里湖區(qū)這個屬性。并且還需確保這個屬性存在于這個對象上, 因此需要咋這兩個類型之間使用約束,
        簡單舉例來說:定義一個函數(shù), 接受兩個參數(shù) 第一個是個對象 obj,第二個個參數(shù)是第一參數(shù) key 是對象里面的鍵名, 需要輸入 obj[key]
        function getProperty<T, K extends keyof T>(obj: T, key: K) {  return obj[key]}
        let obj = { a: 1, b: 2, c: 3 }
        getProperty(obj, 'a') // successgetProperty(obj, 'm') // err obj 中不存在 m 這個參數(shù)
        我們可以為泛型中的類型參數(shù)指定默認類型。當使用泛型時沒有在代碼中直接指定類型參數(shù),從實際值參數(shù)中也無法推測出時,這個默認類型就會起作用
        function createArr<T = string>(length: number, value: T): Array<T> {  let result: T[] = []  for( let i = 0; i < lenght; i++ ) {    result[i] = value  }  return result}
        簡單來說,泛型就是把我們定義時不能夠明確的類型變成一個參數(shù),讓我們在使用的時候再去傳遞這樣一個類型參數(shù)。

        類型聲明

        在項目開發(fā)過程中我們難免會用到一些第三方的npm模塊,而這些npm模塊他不一定都是通過TypeScript編寫的,所以說他所提供的成員呢就不會有強類型的體驗。
        比如我們這里安裝一個lodash的模塊,那這個模塊當中就提供了很多工具函數(shù),安裝完成過后我們回到代碼當中,我們使用ES Module的方式import導(dǎo)入這個模塊,我們這里導(dǎo)入的時候TypeScript就已經(jīng)報出了錯誤,找不到類型聲明的文件,我們暫時忽略。
        這里我們提取一下camelCase的函數(shù),那這個函數(shù)的作用就是把一個字符串轉(zhuǎn)換成駝峰格式,那他的參數(shù)應(yīng)該是一個string,返回值也應(yīng)該是一個string,但是我們在調(diào)用這個函數(shù)逇時候并沒有任何的類型提示。
        import { camelCase } from 'lodash';
        const res = camelCase('hello typed');
        那在這種情況下我們就需要單獨的類型聲明,這里我們可以使用detar語句來去聲明一下這個函數(shù)的類型,具體的語法就是declare function 后面跟上函數(shù)簽名, 參數(shù)類型是input,類型是string,返回值也應(yīng)該是string
        declare function camelCase (input: string ): string;
        那有了這樣一個聲明過后,我們再去使用這個camelCase函數(shù)。這個時候就會有對應(yīng)的類型限制了。
        那這就是所謂的類型聲明,說白了就是一個成員他在定義的時候因為種種原因他沒有聲明一個明確的類型,然后我們在使用的時候我們可以單獨為他再做出一個明確的聲明。
        那這種用法存在的原因呢,就是為了考慮兼容一些普通的js模塊,由于TypeScript的社區(qū)非常強大,目前一些比較常用的npm模塊都已經(jīng)提供了對應(yīng)的聲明,我們只需要安裝一下它所對應(yīng)的這個類型聲明模塊就可以了。
        lodash的報錯模塊已經(jīng)提示了,告訴我們需要安裝一個@types/lodash的模塊,那這個模塊其實就是我們lodash所對應(yīng)的類型聲明模塊,我們可以安裝這個模塊。安裝完成過后,lodash模塊就不會報錯了。在ts中.d.ts文件都是做類型聲明的文件,
        除了類型聲明模塊,現(xiàn)在越來越多的模塊已經(jīng)在內(nèi)部繼承了這種類型的聲明文件,很多時候我們都不需要單獨安裝這種類型聲明模塊。
        例如我們這里安裝一個query-string的模塊,這個模塊的作用就是用來去解析url當中的query-string字符串,那在這個模塊當中他就已經(jīng)包含了類型聲明文件。
        我們這里直接導(dǎo)入這個模塊,我們所導(dǎo)入的這個對象他就直接會有類型約束。
        那以上就是對TypeScript當中所使用第三方模塊類型聲明的介紹。
        那這里我們再來總結(jié)一下,在TypeScript當中我們?nèi)ヒ玫谌侥K,如果這個模塊當中不包含所對應(yīng)的類型聲明文件,那我們就可以嘗試去安裝一個所對應(yīng)的類型聲明模塊,那這個類型聲明模塊一般就是@types/模塊名。
        那如果也沒有這樣一個對應(yīng)的類型聲明模塊,那這種情況下我們就只能自己使用declare語句去聲明所對應(yīng)的模塊類型,那對于declare詳細的語法這里我們不再單獨介紹了,有需要的話可以單獨去查詢一下官方文檔。
        本文完~

        最后


        • 歡迎加我微信(winty230),拉你進技術(shù)群,長期交流學(xué)習(xí)...

        • 歡迎關(guān)注「前端Q」,認真學(xué)前端,做個專業(yè)的技術(shù)人...

        點個在看支持我吧
        瀏覽 48
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
          
          

            1. 亚洲自拍婷婷 | 波霸影院 | 中国一级片 | 国产精品久久久久久久久鸭下载 | 青青草综合视频 |