iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Modern Web

TypeScript 魔法 - 喚醒你的程式碼靈感系列 第 12

Day12 - TypeScript 的秘密武器(下) - 介面(Interfaces)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20230927/20152047Cmwtpgbm0r.png

什麼是介面?

介面是一種用於定義型別結構的抽象概念。它們提供了一種方法來描述物件的形狀,指定它們應該具有哪些屬性和方法。換句話說,介面定義了一個物件應該是什麼樣子,但不關心具體的實現細節。

介面使用範例

在以下的例子中,我們使用 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');

https://ithelp.ithome.com.tw/upload/images/20230927/20152047VU7P3cNncp.png

當定義的變數比介面多了一些屬性(多了 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');

https://ithelp.ithome.com.tw/upload/images/20230927/20152047aMNTP4NFGE.png

唯讀屬性(readonly)

當定義的介面中屬性加上 readonly 表示該屬性被建立後不可被修改(userId 屬性):

interface Person {
  readonly userId: number;
  name: string;
  age: number;
}

const person: Person = {
  userId: 123,
  name: '肉鬆',
  age: 30,
};

person.userId = 456; // TypeScript 報錯

https://ithelp.ithome.com.tw/upload/images/20230927/20152047SNEhyG4yTV.png

可選屬性(?)

當定義的介面中屬性加上 ? 表示該屬性可以存在,也可以不存在。

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)

我們可以使用 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 介面繼承的 nameage 這兩個屬性。

資料定義中的 typeinterface 合併

  1. 使用 type 關鍵字建立一個名為 Gender 的型別別名。這個型別別名被設定為只能取 malefemale 的字串值。
  2. Person 介面中,gender 屬性的型別被設定為我們剛剛定義的型別別名 Gender
  3. 建立一個 person 物件,符合 Person 介面,此時,如果嘗試將 gender 屬性的值設定為除了 malefemale 之外的字串值,TypeScript 將會產生錯誤。
type Gender = 'male' | 'female';

interface Person {
  name: string;
  age: number;
  gender: Gender;
}

const person: Person = {
  name: '肉鬆',
  age: 30,
  gender: 'male', // 若嘗試賦予 male 或 female 外的字串值,TypeScript 會報錯
};

typeinterface 差異

談到 TypeScript 中的型別定義時,主要有兩個選擇:typeinterface。以下是它們之間的主要差異,以便更清楚地理解它們:

Type(型別)

  • 使用 type 來定義型別。
  • 常用於聯合型別、交集型別和模板文字型別。
  • 使用 &| 運算符來組合多個型別。
  • 可命名基本型別、自訂型別、元組、聯合型別等。
  • 可使用 typeof 獲取變數的型別。
  • 可使用泛型(Generics)來創建可重用的型別。
  • 不支持擴展(extends)關鍵字用於類別。
  • 常用於描述具體的值和資料結構。

Interface(介面)

  • 使用 interface 來定義介面。
  • 常用於描述物件的形狀(Shape)。
  • 可用於擴展其他介面。
  • 支持多重繼承(一個介面可以繼承多個介面)。
  • 可包含方法的定義,包括方法名稱、參數和返回值的型別。
  • 不能命名基本型別、聯合型別、交集型別等,僅用於描述物件結構。
  • 不支持 typeof 用於變數,而僅用於類別、函數和命名空間。
  • 常用於描述物件的屬性和方法結構,並且可以實現多態性。

本日重點

  1. 命名規範:以寫字母開頭,有助於與普通變數或函式區分。
  2. 一個物件符合某個介面時,物件的形狀(屬性名稱和型別)必須與介面一致。
  3. readonly 表示該屬性被建立後不可被修改。
  4. ? 表示該屬性可以存在,也可以不存在。
  5. 當索引簽名的型別為 any 時,屬性的值可以是任何型別。反之,如果定義了任意屬性型別,其他屬性和可選屬性的型別必須是該任意屬性型別的子集。
  6. 使用 extends 關鍵字可以擴展一個介面的屬性。

參考


上一篇
Day11 - TypeScript 的秘密武器(上) - 型別別名(Type Aliases)
下一篇
Day13 - 解析函式返回型別的精隨
系列文
TypeScript 魔法 - 喚醒你的程式碼靈感30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言