介面是一種用於定義型別結構的抽象概念。它們提供了一種方法來描述物件的形狀,指定它們應該具有哪些屬性和方法。換句話說,介面定義了一個物件應該是什麼樣子,但不關心具體的實現細節。
在以下的例子中,我們使用 interface
關鍵字來定義了一個介面叫做 Person
。然後,我們宣告了一個名為 person
的變數,並明確指定了它的型別為 Person
。這樣做的目的是約束 person
物件的形狀必須與 Person
介面中所定義的一致。
interface Person {
name: string;
age: number;
greet: (phrase: string) => void;
}
const person: Person = {
name: '肉鬆',
age: 30,
greet: (phrase: string) => {
console.log(`我是${person.name} - ${phrase}`); // 我是肉鬆 - 一起學習 TypeScript
},
};
person.greet('一起學習 TypeScript');
當定義的變數比介面少了一些屬性(少了 age
屬性):
interface Person {
name: string;
age: number;
greet: (phrase: string) => void;
}
const person: Person = { // TypeScript 報錯
name: '肉鬆',
greet: (phrase: string) => {
console.log(`我是${person.name} - ${phrase}`); // 我是肉鬆 - 一起學習 TypeScript
},
};
person.greet('一起學習 TypeScript');
當定義的變數比介面多了一些屬性(多了 gender
屬性):
interface Person {
name: string;
age: number;
greet: (phrase: string) => void;
}
const person: Person = {
name: '肉鬆',
age: 30,
gender: 'male', // TypeScript 報錯
greet: (phrase: string) => {
console.log(`我是${person.name} - ${phrase}`); // 我是肉鬆 - 一起學習 TypeScript
},
};
person.greet('一起學習 TypeScript');
當定義的介面中屬性加上 readonly
表示該屬性被建立後不可被修改(userId
屬性):
interface Person {
readonly userId: number;
name: string;
age: number;
}
const person: Person = {
userId: 123,
name: '肉鬆',
age: 30,
};
person.userId = 456; // TypeScript 報錯
當定義的介面中屬性加上 ?
表示該屬性可以存在,也可以不存在。
age
屬性不存在:
interface Person {
name: string;
age?: number;
}
const person: Person = {
name: '肉鬆',
};
age
屬性存在:
interface Person {
name: string;
age?: number;
}
const person: Person = {
name: '肉鬆',
age: 30,
};
這時仍然不允許新增未定義的屬性(多了 gender
屬性):
const person: Person = {
name: '肉鬆',
age: 30,
gender: 'male', // TypeScript 報錯
};
允許我們定義一個介面,其中可以包含不固定名稱的屬性,並指定它的型別。
當索引簽名型別為 any
:
表示我們可以在物件中包含任何屬性,這些屬性的值可以是任何型別,包括數字、字串、布林、物件、函式等。
interface ErrorContainer1 {
id: number;
[key: string]: any;
}
const errorBag1: ErrorContainer1 = {
id: 123,
email: '無效的信箱格式',
isActive: true,
};
當索引簽名型別為 string
:
表示我們可以在物件包含任何屬性,但這些屬性的值必須都是字串型別。
特別注意:一旦定義任意屬性的型別,確定屬性與可選屬性的型別都必須是該任意屬性型別的子集。
interface ErrorContainer2 {
id: number; // TypeScript 報錯,類型 'number' 的屬性 'id' 不可指派給 'string' 索引類型 'string'
[key: string]: string;
}
const errorBag2: ErrorContainer2 = {
id: '123',
email: '無效的信箱格式',
age: 30, // TypeScript 報錯,類型 'number' 不可指派給類型 'string'
};
讓我們修改程式碼以解決 TypeScript 錯誤:
interface ErrorContainer2 {
id: string;
[key: string]: string;
}
const errorBag2: ErrorContainer2 = {
id: '0123',
email: '無效的信箱格式',
age: '30',
};
我們可以使用 extends
關鍵字將一個介面繼承到另一個介面,從而擴展介面的屬性。
interface Person {
name: string;
age: number;
}
interface User extends Person {
employeeId: string;
}
const user: User = {
name: '肉鬆',
age: 30,
employeeId: '123',
};
在上方的程式碼中,user
物件被定義為符合 User
介面,而 User
介面則繼承了 Person
介面。這意味著 user
物件必須包含 User
介面中的所有屬性,以及由 Person
介面繼承的 name
和 age
這兩個屬性。
type
與 interface
合併type
關鍵字建立一個名為 Gender
的型別別名。這個型別別名被設定為只能取 male
或 female
的字串值。Person
介面中,gender
屬性的型別被設定為我們剛剛定義的型別別名 Gender
。person
物件,符合 Person
介面,此時,如果嘗試將 gender
屬性的值設定為除了 male
或 female
之外的字串值,TypeScript 將會產生錯誤。type Gender = 'male' | 'female';
interface Person {
name: string;
age: number;
gender: Gender;
}
const person: Person = {
name: '肉鬆',
age: 30,
gender: 'male', // 若嘗試賦予 male 或 female 外的字串值,TypeScript 會報錯
};
type
與 interface
差異談到 TypeScript 中的型別定義時,主要有兩個選擇:type
和 interface
。以下是它們之間的主要差異,以便更清楚地理解它們:
type
來定義型別。&
和 |
運算符來組合多個型別。typeof
獲取變數的型別。interface
來定義介面。typeof
用於變數,而僅用於類別、函數和命名空間。readonly
表示該屬性被建立後不可被修改。?
表示該屬性可以存在,也可以不存在。any
時,屬性的值可以是任何型別。反之,如果定義了任意屬性型別,其他屬性和可選屬性的型別必須是該任意屬性型別的子集。extends
關鍵字可以擴展一個介面的屬性。