一、接口的定義
在面向?qū)ο蟮木幊讨?,接口是一種規(guī)范的定義,它定義了行為和動作的規(guī)范,在程序設(shè)計里面,接口起到一種限制和規(guī)范的作用。接口定義了某一批類所需要遵守的規(guī)范,接口不關(guān)心這些類的內(nèi)部狀態(tài)數(shù)據(jù),也不關(guān)心這些類里方法的實現(xiàn)細節(jié),它只規(guī)定這批類里必須提供某些方法,提供這些方法的類就可以滿足實際需要。 typescrip中的接口類似于java,同時還增加了更靈活的接口類型,包括屬性、函數(shù)、可索引和類等。
二、接口的用途
接口的用途就是對行為和動作進行規(guī)范和約束,跟抽象類有點像,但是,接口中不能有方法體,只允許有方法定義。
三、接口用法:
1. 使用interface來定義接口:
interface Info {
firstName: string;
lastName: string;
}
const getFullName = ({ firstName, lastName }: Info) => {
return `${firstName} ${lastName}`;
};
console.log(getFullName({ firstName: '123', lastName: '1231' }));
注意在定義接口的時候,你不要把它理解為是在定義一個對象,而要理解為{}括號包裹的是一個代碼塊,里面是一條條聲明語句,只不過聲明的不是變量的值而是類型。聲明也不用等號賦值,而是冒號指定類型。每條聲明之前用換行分隔即可,或者也可以使用分號或者逗號,都是可以的。
2. 可選屬性
接口設(shè)置可選屬性,在屬性名后面加個?即可:
interface Vegetables {
color?: string;
type: string;
}
3. 多余屬性檢查
getVegetables({
type: "tomato",
size: "big" // 'size'不在類型'Vegetables'中
});
我們看到,傳入的參數(shù)沒有 color 屬性,但也沒有錯誤,因為它是可選屬性。但是我們多傳入了一個 size 屬性,這同樣會報錯,TypeScript 會告訴你,接口上不存在你多余的這個屬性。只要接口中沒有定義這個屬性,就會報錯,但如果你定義了可選屬性 size,那么上面的例子就不會報錯。
4. 繞開多余屬性檢查
- 什么是接口的多余參數(shù)檢查
interface Baseinfo {
name:string,
sex?:string
}
// 人
function printPesonInfo(parmasinfo: Baseinfo) {
console.log(`姓名:${parmasinfo.name }`)
}
// 如果直接傳遞參數(shù),且傳遞的參數(shù)key未在接口中定義會提示錯誤
printPesonInfo( {name:'wang',age:13} ) // 報錯的
- 使用類型斷言
類型斷言就是用來明確告訴 TypeScript,我們已經(jīng)自行進行了檢查,確保這個類型沒有問題,希望 TypeScript 對此不進行檢查,所以最簡單的方式就是使用類型斷言:
interface Baseinfo {
name:string,
sex?:string
}
// 人
function printPesonInfo(parmasinfo: Baseinfo) {
console.log(`姓名:${parmasinfo.name }`)
}
// 利用類型斷言,告訴編譯器我們傳遞的參數(shù) 就是Baseinfo 接口的東西
printPesonInfo( {name:'wang',age:13} as Baseinfo ) // wang
- 索引簽名
interface Baseinfo {
name:string,
sex?:string,
[other:string]:any
}
// 人
function printPesonInfo(parmasinfo: Baseinfo) {
console.log(`姓名:${parmasinfo.name }`)
}
// 接口中的索引簽名other 就會收到age
printPesonInfo( {name:'wang',age:13}) // wang
- 利用類型兼容性
interface Baseinfo {
name:string,
sex?:string,
}
// 人
function printPesonInfo(parmasinfo: Baseinfo) {
console.log(`姓名:${parmasinfo.name }`)
}
let paramsinfo = {name:'wang',age:13}
// 類型兼容性就是我們定義的paramsinfo 不管有都少東西,只要包含接口中定義的即可
printPesonInfo(paramsinfo) // 姓名:wang
5. 只讀屬性
關(guān)鍵字:readonly
const NAME: string = "Lison";
NAME = "Haha"; // Uncaught TypeError: Assignment to constant variable
const obj = {
name: "lison"
};
obj.name = "Haha";
interface Info {
readonly name: string;
}
const info: Info = {
name: "Lison"
};
info["name"] = "Haha"; // Cannot assign to 'name' because it is a read-only property
6. 函數(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ù)列表和返回值類型組成。后面學(xué)到類型別名一節(jié)時我們還會學(xué)習(xí)其他寫法。來看下如何使用:
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ù)類型沒錯,但是返回的是字符串,所以會報錯。而當(dāng)我們調(diào)用add函數(shù)時,傳入的參數(shù)如果和接口定義的類型不一致,也會報錯。
你應(yīng)該注意到了,實際定義函數(shù)的時候,名字是無需和接口中參數(shù)名相同的,只需要位置對應(yīng)即可。
四、接口的高階用法
1. 索引類型
我們可以使用接口描述索引的類型和通過索引得到的值的類型,比如一個數(shù)組[‘a(chǎn)’, ‘b’],數(shù)字索引0對應(yīng)的通過索引得到的值為’a’。我們可以同時給索引和值都設(shè)置類型,看下面的示例:
interface RoleDic {
[id: number]: string;
}
const role1: RoleDic = {
0: "superadmin",
1: "admin"
};
console.log(role1);
const role2: RoleDic = {
s: "superadmin", // error 不能將類型"{ s: string; a: string; }"分配給類型"RoleDic"。
a: "admin"
};
console.log(role2);
const role3: RoleDic = ["super_admin", "admin"];
console.log(role3);
role2 報錯信息:
上面的例子中 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", // 這里定義一個數(shù)值類型的123這個屬性
"123": "b" // 這里在定義一個字符串類型的123這個屬性,這里會報錯:標(biāo)識符“"123"”重復(fù)。
};
console.log(obj); // { '123': 'b' }
如果數(shù)值類型的屬性名不會轉(zhuǎn)為字符串類型,那么這里數(shù)值123和字符串123是不同的兩個值,則最后對象obj應(yīng)該同時有這兩個屬性;但是實際打印出來的obj只有一個屬性,屬性名為字符串"123",而且值為"b",說明數(shù)值類型屬性名123被覆蓋掉了,就是因為它被轉(zhuǎn)為了字符串類型屬性名"123";又因為一個對象中多個相同屬性名的屬性,定義在后面的會覆蓋前面的,所以結(jié)果就是obj只保留了后面定義的屬性值。
2. 繼承接口
接口可以繼承,這和類一樣,這提高了接口的可復(fù)用性。來看一個場景:
我們定義一個Vegetables接口,它會對color屬性進行限制。再定義兩個接口,一個為Tomato,一個為Carrot,這兩個類都需要對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.2
}; // 在定義tomato變量時將繼承過來的color和type屬性同時聲明
3. 混合類型接口
JS 中,函數(shù)是對象類型。對象可以有屬性,所以有時我們的一個對象,它既是一個函數(shù),也包含一些屬性。比如我們要實現(xiàn)一個計數(shù)器函數(shù),比較直接的做法是定義一個函數(shù)和一個全局變量:
let count = 0;
const countUp = () => count++;
但是這種方法需要在函數(shù)外面定義一個變量,更優(yōu)一點的方法是使用閉包:
// javascript
const countUp = (() => {
let count = 0;
return () => {
return ++count;
};
})();
console.log(countUp()); // 1
console.log(countUp()); // 2
// javascript
let countUp = () => {
return ++countUp.count;
};
countUp.count = 0;
console.log(countUp()); // 1
console.log(countUp()); // 2
我們可以看到,我們把一個函數(shù)賦值給countUp,又給它綁定了一個屬性count,我們的計數(shù)保存在這個 count 屬性中。
我們可以使用混合類型接口來指定上面例子中 countUp 的類型:
interface Counter {
(): void; // 這里定義Counter這個結(jié)構(gòu)必須包含一個函數(shù),函數(shù)的要求是無參數(shù),返回值為void,即無返回值
count: number; // 而且這個結(jié)構(gòu)還必須包含一個名為count、值的類型為number類型的屬性
}
const getCounter = (): Counter => { // 這里定義一個函數(shù)用來返回這個計數(shù)器
const c = () => { // 定義一個函數(shù),邏輯和前面例子的一樣
c.count++;
};
c.count = 0; // 再給這個函數(shù)添加一個count屬性初始值為0
return c; // 最后返回這個函數(shù)對象
};
const counter: Counter = getCounter(); // 通過getCounter函數(shù)得到這個計數(shù)器
counter();
console.log(counter.count); // 1
counter();
console.log(counter.count); // 2
上面的例子中,getCounter函數(shù)返回值類型為Counter,它是一個函數(shù),無返回值,即返回值類型為void,它還包含一個屬性count,屬性返回值類型為number。
五、類型別名
類型別名就是給一種類型起個別的名字,之后只要使用這個類型的地方,都可以用這個名字作為類型代替,但是它只是起了一個名字,并不是創(chuàng)建了一個新類型。這種感覺就像 JS 中對象的賦值,你可以把一個對象賦給一個變量,使用這個對象的地方都可以用這個變量代替,但你并不是創(chuàng)建了一個新對象,而是通過引用來使用這個對象。
使用 type 關(guān)鍵字,定義類型別名:
type TypeString = string;
let str: TypeString;
str = 123; // error Type '123' is not assignable to type 'string'
- 類型別名也可以使用泛型:
type PositionType<T> = { x: T; y: T };
const position1: PositionType<number> = {
x: 1,
y: -1
};
const position2: PositionType<string> = {
x: "right",
y: "top"
};
- 使用類型別名時也可以在屬性中引用自己:
type Child<T> = {
current: T;
child?: Child<T>;
};
let ccc: Child<string> = {
current: "first",
child: {
// error
current: "second",
child: {
current: "third",
child: "test" // 這個地方不符合type,造成最外層child處報錯
}
}
};
- 但是要注意,只可以在對象屬性中引用類型別名自己,不能直接使用,比如下面這樣是不對的:
type Child = Child[]; // error 類型別名“Child”循環(huán)引用自身
因為類型別名只是為其它類型起了個新名字來引用這個類型,所以當(dāng)它為接口起別名時,不能使用 extends 和 implements 。
- 接口和類型別名有時可以起到同樣作用,比如下面這個例子:
type Alias = {
num: number;
};
interface Interface {
num: number;
}
let alias: Alias = {
num: 123
};
let interface: Interface = {
num: 321
};
alias = interface;
可以看到用類型別名和接口都可以定義一個只包含 num 屬性的對象類型,而且類型是兼容的。那么什么時候用類型別名,什么時候用接口呢?可以通過兩點來選擇:
- 當(dāng)你定義的類型要用于拓展,即使用 implements 等修飾符時,用接口。
- 當(dāng)無法通過接口,并且需要使用聯(lián)合類型或元組類型,用類型別名。
六、字面量類型
1. 字符串字面量類型
字符串字面量類型其實就是字符串常量,與字符串類型不同的是它是具體的值。
type Name = "Lison";
const name1: Name = "test"; // error 不能將類型“"test"”分配給類型“"Lison"”
console.log(name1);
const name2: Name = "Lison";
console.log(name2);
使用聯(lián)合類型來使用多個字符串:
type Direction = "north" | "east" | "south" | "west";
function getDirectionFirstLetter(direction: Direction) {
return direction.substr(0, 1);
}
getDirectionFirstLetter("test"); // error 類型“"test"”的參數(shù)不能賦給類型“Direction”的參數(shù)
getDirectionFirstLetter("east");
2. 數(shù)字字面量類型
另一個字面量類型就是數(shù)字字面量類型,它和字符串字面量類型差不多,都是指定類型為具體的值。
type Age = 18;
interface Info {
name: string;
age: Age;
}
const info: Info = {
name: "Lison",
age: 28 // error 不能將類型“28”分配給類型“18”
};
這里補充一個比較經(jīng)典的邏輯錯誤,來看例子:
function getValue(index: number) {
if (index !== 0 || index !== 1) {
// error This condition will always return 'true' since the types '0' and '1' have no overlap
// ...
}
}
這個例子中,在判斷邏輯處使用了 || 符,當(dāng) index !== 0 不成立時,說明 index 就是 0,則不應(yīng)該再判斷 index 是否不等于 1;而如果 index !== 0 成立,那后面的判斷也不會再執(zhí)行;所以這個地方會報錯。
七、Interface 與 Type 的區(qū)別
1. 區(qū)別
- 接口可以重復(fù)定義的接口類型,它的屬性會疊加,類型別名不行
interface Language {
id: number
}
interface Language {
name: string
}
let lang: Language = {
id: 1, // ok
name: 'name', // ok
}
// 如果使用類型別名
/** ts(2300) 重復(fù)的標(biāo)志 */
type Language = {
id: number
}
/** ts(2300) 重復(fù)的標(biāo)志 */
type Language = {
name: string
}
let lang: Language = {
id: 1,
name: 'name',
}
- type 可以使用聯(lián)合類型和交集,interface 不能使用聯(lián)合類型和交集組合
type Pet = Dog | Cat
// 具體定義數(shù)組每個位置的類型
type PetList = [Dog, Pet]
- type 支持類型映射,interface不支持
type Keys = "firstname" | "surname"
type DudeType = {
[key in Keys]: string
}
const test: DudeType = {
firstname: "Pawel",
surname: "Grzybek"
}
// 報錯
//interface DudeType {
// [key in keys]: string
//}
2. 相同點
都允許拓展(extends)
interface 和 type 都可以拓展,并且兩者并不是相互獨立的,也就是說 interface 可以 extends type, type 也可以 extends interface , 雖然效果差不多,但是兩者語法不同。
- interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
- type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
- interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
- type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}
總結(jié):
interface 只能用于定義對象類型和方法,而 type 的聲明方式除了對象之外還可以定義交叉、聯(lián)合、原始類型等,類型聲明的方式適用范圍顯然更加廣泛。文章來源:http://www.zghlxwxcb.cn/news/detail-566939.html
但是interface也有其特定的用處:
interface 方式可以實現(xiàn)接口的 extends 和 implements
interface 可以實現(xiàn)接口合并聲明文章來源地址http://www.zghlxwxcb.cn/news/detail-566939.html
到了這里,關(guān)于TypeScript 學(xué)習(xí)筆記(二):接口與類型別名、字面量類型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!