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 接口 interface 使用詳解

        共 10409字,需瀏覽 21分鐘

         ·

        2022-02-14 23:15


        前端獵手
        ?鏈接每一位開發(fā)者,讓編程更有趣兒!
        關(guān)注

        我是法醫(yī),一只治療系前端碼猿??,與代碼對話,傾聽它們心底的呼聲,期待著大家的點贊??與關(guān)注?,當然也歡迎加入前端獵手技術(shù)交流群??,文末掃碼我拉你進群,一起交流技術(shù)以及代碼之外的一切???♀?

        在 TypeScript中,接口是一個很重要的特性,它讓 TypeScript 具備了 JavaScript 所缺少的、描述較為復雜數(shù)據(jù)結(jié)構(gòu)的能力。下面就來看看什么是接口類型。

        一、接口定義

        接口是一系列抽象方法的聲明,是一些方法特征的集合,這些方法都應該是抽象的,需要由具體的類去實現(xiàn),然后第三方就可以通過這組抽象方法調(diào)用,讓具體的類執(zhí)行具體的方法。

        TypeScript 的核心原則之一是對值所具有的結(jié)構(gòu)進行類型檢查,并且只要兩個對象的結(jié)構(gòu)一致,屬性和方法的類型一致,則它們的類型就是一致的。?在TypeScript里,接口的作用就是為這些類型命名和為代碼或第三方代碼定義契約。

        TypeScript 接口定義形式如下:

        interface?interface_name?{?}

        來看例子,函數(shù)的參數(shù)是一個對象,它包含兩個字段:firstName 和 lastName,返回一個拼接后的完整名字:

        const?getFullName?=?({?firstName,?lastName?})?=>?{
        ??return?`${firstName}?${lastName}`;
        };

        調(diào)用時傳入?yún)?shù):

        getFullName({
        ??firstName:?"Hello",
        ??lastName:?"TypeScript"
        });?

        這樣調(diào)用是沒有問題的,但是如果傳入的參數(shù)不是想要的參數(shù)格式時,就會出現(xiàn)一些錯誤:

        getFullName();?//?Uncaught?TypeError:?Cannot?destructure?property?`a`?of?'undefined'?or?'null'.
        getFullName({?age:?18,?phone:?110?});?//?'undefined?undefined'
        getFullName({?firstName:?"Hello"?});?//?'Hello?undefined'

        這些都是我們不想要的,在開發(fā)時難免會傳入錯誤的參數(shù),所以 TypeScript 能夠在編譯階段就檢測到這些錯誤。下面來完善下這個函數(shù)的定義:

        const?getFullName?=?({
        ??firstName,
        ??lastName,
        }:?{
        ??firstName:?string;?//?指定屬性名為firstName和lastName的字段的屬性值必須為string類型
        ??lastName:?string;
        })?=>?{
        ??return?`${firstName}?${lastName}`;
        };

        通過對象字面量的形式去限定傳入的這個對象的結(jié)構(gòu),現(xiàn)在再來看下之前的調(diào)用會出現(xiàn)什么提示:

        getFullName();?//?應有1個參數(shù),但獲得0個
        getFullName({?age:?18,?phone:?110?});?//?類型“{ age: number; phone: number;?}”的參數(shù)不能賦給類型“{ firstName: string; lastName: string;?}”的參數(shù)。
        getFullName({?firstName:?"Hello"?});?//?缺少必要屬性lastName

        這些都是在編寫代碼時 TypeScript 提示的錯誤信息,這樣就避免了在使用函數(shù)的時候傳入不正確的參數(shù)。我們可以使用interface來定義接口:

        interface?Info?{
        ??firstName:?string;
        ??lastName:?string;
        }
        const?getFullName?=?({?firstName,?lastName?}:?Info)?=>
        ??return?`${firstName}?${lastName}`;
        };

        注意:在定義接口時,不要把它理解為是在定義一個對象,{}括號包裹的是一個代碼塊,里面是聲明語句,只不過聲明的不是變量的值而是類型。聲明也不用等號賦值,而是冒號指定類型。每條聲明之前用換行分隔即可,也可以使用分號或者逗號。

        二、接口屬性

        1. 可選屬性

        在定義一些結(jié)構(gòu)時,一些結(jié)構(gòu)的某些字段的要求是可選的,有這個字段就做處理,沒有就忽略,所以針對這種情況,TypeScript提供了可選屬性。

        定義一個函數(shù):

        const?getVegetables?=?({?color,?type?})?=>?{
        ??return?`A?${color???color?+?"?"?:?""}${type}`;
        };

        這個函數(shù)中根據(jù)傳入對象中的 color 和 type 來進行描述返回一句話,color 是可選的,所以可以給接口設(shè)置可選屬性,在屬性名后面加個?即可:

        interface?Vegetables?{
        ??color?:?string;
        ??type:?string;
        }

        const?getVegetables?=?({?color,?type?}:?Vegetables)?=>?{
        ??return?`A?${color???color?+?"?"?:?""}${type}`;
        };

        這里可能會報一個警告:接口應該以大寫的i開頭,可以在 tslint.json 的 rules 里添加"interface-name": [true, “never-prefix”]來關(guān)閉這條規(guī)則。

        當屬性被標注為可選后,它的類型就變成了顯式指定的類型與 undefined 類型組成的聯(lián)合類型,比如 getVegetables 方法的參數(shù)中的 color 屬性類型就變成了這樣:

        string?|?undefined;

        那下面的接口與上述接口是一樣的嗎?

        interface?Vegetables2?{
        ??color?:?string?|?undefined;
        ??type:?string;
        }

        答案肯定是否定的,因為可選意味著可以不設(shè)置屬性鍵名,類型是 undefined 意味著屬性鍵名不可選。

        2. 只讀屬性

        接口可以設(shè)置只讀屬性,如下:

        interface?Role?{
        ??readonly?0:?string;
        ??readonly?1:?string;
        }

        這里定義了一個角色,有 0 和 1 兩種角色 id。下面定義一個角色數(shù)據(jù),并修改一下它的值:

        const?role:?Role?=?{
        ??0:?"super_admin",
        ??1:?"admin"
        };
        role[1]?=?"super_admin";?//?Cannot?assign?to?'0'?because?it?is?a?read-only?property

        這里TypeScript 提示不能分配給索引0,因為它是只讀屬性。

        在ES6中,使用const定義的常量定義之后不能再修改,這和只讀意思接近。那readonlyconst在使用時該如何選擇呢?那主要看這個值的用途,如果是定義一個常量,那用const,如果這個值是作為對象的屬性,就用readonly

        const?NAME:?string?=?"TypeScript";
        NAME?=?"Haha";?//?Uncaught?TypeError:?Assignment?to?constant?variable
        const?obj?=?{
        ??name:?"TypeScript"
        };
        obj.name?=?"Haha";

        interface?Info?{
        ??readonly?name:?string;
        }
        const?info:?Info?=?{
        ??name:?"TypeScript"
        };
        info["name"]?=?"Haha";?//?Cannot?assign?to?'name'?because?it?is?a?read-only?property

        上面使用const定義的常量NAME定義之后再修改會報錯,但是如果使用const定義一個對象,然后修改對象里屬性的值是不會報錯的。所以如果要保證對象的屬性值不可修改,需要使用readonly

        需要注意,readonly只是靜態(tài)類型檢測層面的只讀,實際上并不能阻止對對象的修改。因為在轉(zhuǎn)譯為 JavaScript 之后,readonly 修飾符會被抹除。因此,任何時候與其直接修改一個對象,不如返回一個新的對象,這是一種比較安全的方式。

        3. 多余屬性檢查

        先來看下面的例子:

        interface?Vegetables?{
        ??color?:?string;
        ??type:?string;
        }

        const?getVegetables?=?({?color,?type?}:?Vegetables)?=>?{
        ??return?`A?${color???color?+?"?"?:?""}${type}`;
        };

        getVegetables({
        ??type:?"tomato",
        ??size:?"big"?????//?'size'不在類型'Vegetables'中
        });

        這里沒有傳入 color 屬性,因為它是一個可選屬性,所以沒有報錯。但是多傳入了一個 size 屬性,這時就會報錯,TypeScript就會提示接口上不存在這個多余的屬性,所以只要是接口上沒有定義這個屬性,在調(diào)用時出現(xiàn)了,就會報錯。

        注意:?這里可能會報一個警告:屬性名沒有按開頭字母順序排列屬性列表,可以在 tslint.json 的 rules 里添加"object-literal-sort-keys": [false]來關(guān)閉這條規(guī)則。

        有時 不希望TypeScript這么嚴格的對數(shù)據(jù)進行檢查,比如上面的函數(shù),只需要保證傳入getVegetables的對象有type屬性就可以了,至于實際使用的時候傳入對象有沒有多余的屬性,多余屬性的屬性值是什么類型,我們就不管了,那就需要繞開多余屬性檢查,有如下方式:

        (1) 使用類型斷言

        類型斷言就是告訴 TypeScript,已經(jīng)自行進行了檢查,確保這個類型沒有問題,希望 TypeScript 對此不進行檢查,這是最簡單的實現(xiàn)方式,類型斷言使用 as 關(guān)鍵字來定義(這里不細說,后面進階篇會專門介紹類型斷言):

        interface?Vegetables?{
        ??color?:?string;
        ??type:?string;
        }

        const?getVegetables?=?({?color,?type?}:?Vegetables)?=>?{
        ??return?`A?${color???color?+?"?"?:?""}${type}`;
        };

        getVegetables({
        ??type:?"tomato",
        ??size:?12,
        ??price:?1.2
        }?as?Vegetables);

        (2) 添加索引簽名

        更好的方式是添加字符串索引簽名:

        interface?Vegetables?{
        ??color:?string;
        ??type:?string;
        ??[prop:?string]:?any;
        }

        const?getVegetables?=?({?color,?type?}:?Vegetables)?=>?{
        ??return?`A?${color???color?+?"?"?:?""}${type}`;
        };

        getVegetables({
        ??color:?"red",
        ??type:?"tomato",
        ??size:?12,
        ??price:?1.2
        });

        三、接口使用

        1. 定義函數(shù)類型

        在前面函數(shù)類型篇我們說了,可以使用接口來定義函數(shù)類型:

        interface?AddFunc?{
        ??(num1:?number,?num2:?number):?number;
        }

        這里定義了一個AddFunc結(jié)構(gòu),這個結(jié)構(gòu)要求實現(xiàn)這個結(jié)構(gòu)的值,必須包含一個和結(jié)構(gòu)里定義的函數(shù)一樣參數(shù)、一樣返回值的方法,或者這個值就是符合這個函數(shù)要求的函數(shù)。把花括號里包著的內(nèi)容稱為調(diào)用簽名,它由帶有參數(shù)類型的參數(shù)列表和返回值類型組成:

        const?add:?AddFunc?=?(n1,?n2)?=>?n1?+?n2;
        const?join:?AddFunc?=?(n1,?n2)?=>?`${n1}?${n2}`;?//?不能將類型'string'分配給類型'number'
        add("a",?2);?//?類型'string'的參數(shù)不能賦給類型'number'的參數(shù)

        上面定義的add函數(shù)接收兩個數(shù)值類型的參數(shù),返回的結(jié)果也是數(shù)值類型,所以沒有問題。而join函數(shù)參數(shù)類型沒錯,但是返回的是字符串,所以會報錯。而當調(diào)用add函數(shù)時,傳入的參數(shù)如果和接口定義的類型不一致,也會報錯。在實際定義函數(shù)的時候,名字是無需和接口中參數(shù)名相同的,只需要位置對應即可。

        實際上,很少使用接口類型來定義函數(shù)類型,更多使用內(nèi)聯(lián)類型或類型別名配合箭頭函數(shù)語法來定義函數(shù)類型:

        type?AddFunc?=?(num1:?number,?num2:?number)?=>?number;


        這里給箭頭函數(shù)類型指定了一個別名 AddFunc,在其他地方就可以直接復用 AddFunc,而不用重新聲明新的箭頭函數(shù)類型定義。

        2. 定義索引類型

        在實際工作中,使用接口類型較多的地方是對象,比如 React 組件的 Props & State等,這些對象有一個共性,即所有的屬性名、方法名都是確定的。

        實際上,經(jīng)常會把對象當 Map 映射使用,比如下邊代碼中定義了索引是任意數(shù)字的對象 role1 和索引是任意字符串的對象 role2。

        const?role1?=?{
        ??0:?"super_admin",
        ??1:?"admin"
        };
        const?role2?=?{
        ??s:?"super_admin",??
        ??a:?"admin"
        };

        這時需要使用索引簽名來定義上邊提到的對象映射結(jié)構(gòu),并通過 “[索引名: 類型]”的格式約束索引的類型。索引名稱的類型分為 string 和 number 兩種,通過如下定義的 RoleDic 和 RoleDic1 兩個接口,可以用來描述索引是任意數(shù)字或任意字符串的對象:

        interface?RoleDic?{
        ??[id:?number]:?string;
        }

        interface?RoleDic1?{
        ??[id:?string]:?string;
        }

        const?role1:?RoleDic?=?{
        ??0:?"super_admin",
        ??1:?"admin"
        };

        const?role2:?RoleDic?=?{
        ??s:?"super_admin",??// error 不能將類型"{ s: string; a: string;?}"分配給類型"RoleDic"。
        ??a:?"admin"
        };

        const?role3:?RoleDic?=?["super_admin",?"admin"];

        需要注意,當使用數(shù)字作為對象索引時,它的類型既可以與數(shù)字兼容,也可以與字符串兼容,這與 JavaScript 的行為一致。因此,使用 0 或 '0' 索引對象時,這兩者等價。

        上面的 role3 定義了一個數(shù)組,索引為數(shù)值類型,值為字符串類型。我們還可以給索引設(shè)置readonly,從而防止索引返回值被修改:

        interface?RoleDic?{
        ??readonly?[id:?number]:?string;
        }

        const?role:?RoleDic?=?{
        ??0:?"super_admin"
        };

        role[0]?=?"admin";?//?error?類型"RoleDic"中的索引簽名僅允許讀取

        注意,可以設(shè)置索引類型為 number。但是這樣如果將屬性名設(shè)置為字符串類型,則會報錯;但是如果設(shè)置索引類型為字符串類型,那么即便屬性名設(shè)置的是數(shù)值類型,也沒問題。因為 JS 在訪問屬性值時,如果屬性名是數(shù)值類型,會先將數(shù)值類型轉(zhuǎn)為字符串,然后再去訪問:

        const?obj?=?{
        ??123:?"a",?
        ??"123":?"b"?//?報錯:標識符“"123"”重復。
        };
        console.log(obj);?//?{?'123':?'b'?}

        如果數(shù)值類型的屬性名不會轉(zhuǎn)為字符串類型,那么這里數(shù)值123和字符串123是不同的兩個值,則最后對象obj應該同時有這兩個屬性;但是實際打印出來的obj只有一個屬性,屬性名為字符串"123",值為"b",說明數(shù)值類型屬性名123被覆蓋掉了,就是因為它被轉(zhuǎn)為了字符串類型屬性名"123";又因為一個對象中多個相同屬性名的屬性,定義在后面的會覆蓋前面的,所以結(jié)果就是obj只保留了后面定義的屬性值。

        四、高級用法

        1. 繼承接口

        在 TypeScript 中,接口是可以繼承,這和ES6中的類一樣,這提高了接口的可復用性。來看一個場景:定義一個Vegetables接口,它會對color屬性進行限制。再定義兩個接口TomatoCarrot,這兩個類都需要對color進行限制,而各自又有各自獨有的屬性限制,可以這樣定義:

        interface?Vegetables?{
        ??color:?string;
        }

        interface?Tomato?{
        ??color:?string;
        ??radius:?number;
        }

        interface?Carrot?{
        ??color:?string;
        ??length:?number;
        }

        三個接口中都有對color的定義,但是這樣寫很繁瑣,可以用繼承來改寫:

        interface?Vegetables?{
        ??color:?string;
        }

        interface?Tomato?extends?Vegetables?{
        ??radius:?number;
        }

        interface?Carrot?extends?Vegetables?{
        ??length:?number;
        }

        const?tomato:?Tomato?=?{
        ??radius:?1.2?//?error??Property?'color'?is?missing?in?type?'{?radius:?number;?}'
        };

        const?carrot:?Carrot?=?{
        ??color:?"orange",
        ??length:?20
        };

        上面定義的?tomato?變量因為缺少了從Vegetables接口繼承來的?color?屬性,所以報錯了。

        一個接口可以被多個接口繼承,同樣,一個接口也可以繼承多個接口,多個接口用逗號隔開。比如再定義一個Food接口,Tomato?也可以繼承?Food

        interface?Vegetables?{
        ??color:?string;
        }

        interface?Food?{
        ??type:?string;
        }

        interface?Tomato?extends?Food,?Vegetables?{
        ??radius:?number;
        }

        const?tomato:?Tomato?=?{
        ??type:?"vegetables",
        ??color:?"red",
        ??radius:?1
        };??

        如果想要覆蓋掉繼承的屬性,那就只能使用兼容的類型進行覆蓋:

        interface?Tomato?extends?Vegetables?{
        ??color:?number;
        }

        這里我們將color屬性進行了覆蓋,并將其類型設(shè)置為了number類型,這時就會報錯,因為Tomato 和 Vegetables 中的name屬性是不兼容的。

        2. 混合類型接口

        在 JavaScript 中,函數(shù)是對象類型。對象可以有屬性,所以有時一個對象既是一個函數(shù),也包含一些屬性。比如要實現(xiàn)一個計數(shù)器函數(shù),比較直接的做法是定義一個函數(shù)和一個全局變量:

        let?count?=?0;
        const?counter?=?()?=>?count++;

        但是這種方法需要在函數(shù)外面定義一個變量,更優(yōu)一點的方法是使用閉包:

        const?counter?=?(()?=>?{
        ??let?count?=?0;
        ??return?()?=>?{
        ????return?count++;
        ??};
        }
        )();
        console.log(counter());?//?1
        console.log(counter());?//?2

        TypeScript 支持直接給函數(shù)添加屬性,這在 JavaScript 中是支持的:

        let?counter?=?()?=>?{
        ??return?counter.count++;
        };
        counter.count?=?0;
        console.log(counter());?//?1
        console.log(counter());?//?2

        這里把一個函數(shù)賦值給countUp,又給它綁定了一個屬性count,計數(shù)保存在這個?count?屬性中。

        可以使用混合類型接口來指定上面例子中?counter?的類型:

        interface?Counter?{
        ??():?void;?
        ??count:?number;?
        }

        const?getCounter?=?():?Counter?=>?{?
        ??const?c?=?()?=>?{?
        ????c.count++;
        ??};
        ??c.count?=?0;?
        ??return?c;?
        };
        const?counter:?Counter?=?getCounter();?
        counter();
        console.log(counter.count);?//?1
        counter();
        console.log(counter.count);?//?2

        這里定義了一個Counter接口,這個結(jié)構(gòu)必須包含一個函數(shù),函數(shù)的要求是無參數(shù),返回值為void,即無返回值。而且這個結(jié)構(gòu)還必須包含一個名為count、值的類型為number類型的屬性。最后,通過getCounter函數(shù)得到這個計數(shù)器。這里?getCounter?的類型為Counter,它是一個函數(shù),無返回值,即返回值類型為void,它還包含一個屬性count,屬性返回值類型為number。

        五、類型別名

        類型別名并不屬于接口類型的內(nèi)容,但是它和接口功能類似,所以這里放在一起來說。

        1. 基本使用

        接口類型的作用就是將內(nèi)聯(lián)類型抽離出來,從而實現(xiàn)類型復用。其實,還可以使用類型別名接收抽離出來的內(nèi)聯(lián)類型實現(xiàn)復用??梢酝ㄟ^如下所示“type 別名名字 = 類型定義”的格式來定義類型別名,比如上面定義的計數(shù)器方法的類型:

        type?Counter?=?{
        ??():?void;?
        ??count:?number;?
        }

        這樣寫看起來就像是在JavaScript中定義變量,只不過是把var、let、const換成了type。實際上,類型別名可以在接口類型無法覆蓋的場景中使用,比如聯(lián)合類型、交叉類型等:

        //?聯(lián)合類型
        type?Name?=?number?|?string;

        //?交叉類型
        type?Vegetables?=?{color:?string,?radius:?number}?&?{color:?string,?length:?number}

        這里定義了一個 Vegetables 類型別名,表示兩個匿名接口類型交叉出的類型。

        需要注意:類型別名只是給類型取了一個別名,并不是創(chuàng)建了一個新的類型。

        2. 與接口區(qū)別

        通過上面的介紹,可以發(fā)現(xiàn)多數(shù)情況下是可以使用類型別名來替代的,那這是否說明這兩者是等價的呢?答案肯定是否定的,不然也不會出這兩個概念。在某些特定的場景下,這兩者還是存在很大區(qū)別。比如,重復定義的接口類型,它的屬性會疊加,這個特性使得我們可以很方便地對全局變量、第三方庫的類型做擴展:

        interface?Vegetables?{
        ??color:?string;
        }

        interface?Vegetables?{
        ??radius:?number;
        }

        interface?Vegetables?{
        ??length:?number;
        }

        let?vegetables:?Vegetables?=?{
        ?color:??"red",
        ??radius:?2,
        ??length:?10
        }

        這里我們定義了三次 Vegetables 接口,此時可以賦值給變一個包含color、radius、length的對象,并且不會報錯。

        如果重復定義類型別名:

        type?Vegetables?=?{
        ??color:?string;
        }

        type?Vegetables?=?{
        ??radius:?number;
        }

        type?Vegetables?=?{
        ??length:?number;
        }

        let?vegetables:?Vegetables?=?{
        ?color:??"red",
        ??radius:?2,
        ??length:?10
        }

        上述代碼就會報錯:'Vegetables' is already defined。所以,接口類型是可重復定義且屬性會疊加的,而類型別名是不可重復定義的。



        很感謝小伙伴看到最后??,如果您覺得這篇文章有幫助到您的的話不妨關(guān)注?+點贊??+收藏??+評論??,您的支持就是我更新的最大動力。

        歡迎加入前端獵手技術(shù)交流群??,文末掃碼加我微信,我拉你進群,一起交流技術(shù)以及代碼之外的一切???♀?

        瀏覽 92
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        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>
            中文字幕在线观看无码 | 日韩成人网站 | 日韩中文字幕熟妇人妻 | 澳门黄色网 | 色性影院 | 东莞毛片| 性欧美大战久久久久久久83 | 张丽被黑人玩的嗷嗷叫 | 亚欧久久| 精品国语对白69 |