iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0

在 TypeScript 中,我們可以使用 interface 關鍵字來聲明自定義型別,用來描述物件的結構和形狀,以及物件應該具備的屬性和方法。我們可以使用 interface 來聲明變數、函數參數、函數返回值等,以確保相應的物件符合interface 的結構。和 type 一樣,威爾豬喜歡在 interface 加上前綴字 I 來區分這是使用 interface 聲明的型別。(一樣官方不推薦加上前綴就是~)

interface 到底應該翻譯為接口還是介面,威爾豬自己也不清楚,兩種說法都有人使用,但官方的簡體中文翻譯為接口,所以威爾豬就以接口來表示 interface 了。

通常我們會使用 interface 來 聲明物件應該具有哪些屬性和屬性類型,看以下範例:

interface IPerson {
  name: string;
  age: number;
  job: string;
}

const person: IPerson = {
  name: "威爾豬",
  age: 3,
  job: "首富",
};

const greet = (person: IPerson): string =>
  `${person.name},你才 ${person.age} 歲就成為了${person.job}`;

console.log(greet(person)); // 輸出: 威爾豬,你才 3 歲就成為了首富

注意:與 type 寫法稍有不同,聲明 interface 不需使用 =

巢狀物件

我們也經常會使用到巢狀物件的結構,那該如何聲明呢?

interface IStudent {
  name: string;
  age: number;
  courses: {
    math: number;
    design: number;
    history: number;
  };
}

const student: IStudent = {
  name: "威爾豬",
  age: 5,
  courses: {
    math: 65,
    design: 95,
    history: 80,
  },
};

console.log(student.courses.design); // 輸出: 95

在這個例子中,我們使用 IStudent 接口聲明了物件的型別,並在其中嵌套了一個 courses 屬性,該屬性本身也是一個物件。

interface 還可以聲明可選屬性 ? 和唯讀屬性 readonly,使物件的聲明更具靈活性和約束性。

interface IBook {
  title: string;
  author: string;
  year: number;
  isEbook?: boolean; // 可選屬性
  readonly ISBN: string; // 唯讀屬性
}

const book: IBook = {
  title: "用 30 天學會基本 TypeScript",
  author: "威爾豬",
  year: 2023,
  ISBN: "ABC-1234567890",
};

在上面的例子中,isEbook 是一個可選屬性,可以有也可以沒有。而 ISBN 是唯讀屬性,一旦賦值就不能修改。

嵌套物件

在 TypeScript 中,物件還可以包含其他物件作為其屬性,看以下範例:

interface IAddress {
  street: string;
  city: string;
  zipCode: string;
}

interface IContact {
  email: string;
  phone: string;
}

interface IPerson {
  name: string;
  address: IAddress;
  contact: IContact;
}

const person: IPerson = {
  name: "威爾豬",
  address: {
    street: "皇后大道 123 街",
    city: "墨爾本",
    zipCode: "001",
  },
  contact: {
    email: "willPig@example.com",
    phone: "0498765432",
  },
};

IPerson 接口裡,包含了 IAddress 和 IContact 接口的屬性。

函式接口

除了描述物件的結構,interface 還可以描述函式的結構和型別。這對於函式的參數和返回值進行型別檢查和代碼提示非常有用。以下是在 interface 中使用函式型別的範例:

interface ICalculator {
  (a: number, b: number): number;
}

const increase: ICalculator = (a, b) => a + b;
const decrease: ICalculator = (a, b) => a - b;

console.log(increase(2, 3)); // 輸出: 5
console.log(decrease(2, 3)); // 輸出: -1

在這個例子中,我們定義了一個 ICalculator 接口,它描述了接受兩個參數並返回 number 型別的函式型別。

繼承接口

接口可以繼承其他接口,從而將多個接口的屬性和方法結合在一個接口中。還記得 type 是用 & 來進行擴展吧,而 interface 就是直接使用 extends 來實現繼承,我們看以下範例:

interface IAnimal {
  name: string;
  sounds: string;
}

interface IBird extends IAnimal {
  fly(): void;
}

const eagle: IBird = {
  name: "小白鷹",
  sounds: "嘰嘰喳喳",
  fly() {
    console.log("我會飛!");
  },
};

const cat: IAnimal = {
  name: "小藍貓",
  sounds: "喵喵",
};

在上面的例子中,IBird 接口繼承了 IAnimal 接口,並添加了一個 fly 方法。這樣就 只有定義為 IBird 接口的動物才會有 fly 這個方法 可以使用。

https://ithelp.ithome.com.tw/upload/images/20230912/201412505Oznmm489F.png

當然我們也可以實現多繼承,以下範例:

interface IWidth {
  width: number;
}

interface IHeight {
  height: number;
}

interface IRect extends IWidth, IHeight {
  color: string;
}

let rect: IRect = {
  width: 100,
  height: 100,
  color: "red",
};

上面的例子即為 IRect 接口同時繼承了 IWidthIHeight 兩個接口。

類別接口

在 TypeScript 中,類別可以使用 implements 來實現接口,表示該類別符合接口聲明的契約。以下是在類別中實現接口的範例:

interface IShape {
  getArea(): number;
}

class Circle implements IShape {
  constructor(private radius: number) {}

  getArea() {
    let area = Math.round(Math.PI * this.radius ** 2 * 100) / 100;
    console.log(area); // 輸出: 314.16
    return area;
  }
}

const circle = new Circle(10);
circle.getArea();

上面的例子,我們聲明了一個 IShape 接口,它有一個 getArea 方法。然後,我們創建了一個 Circle 類別,並實現 Shape 接口的要求。

泛型接口

我們也可以使用泛型來創建具有彈性的接口,能夠應用於多種型別。泛型接口允許我們在內部使用型別參數,以便於後續指定具體的型別。以下是使用泛型接口的範例:

interface IGeneric<T> {
  value: T;
}

const stringBox: IGeneric<string> = { value: "威爾豬" };
const numberBox: IGeneric<number> = { value: 3 };

在這個例子中,我們定義了一個泛型接口 IGeneric<T>,它有一個 value 屬性,型別由泛型參數 T 決定。然後,我們創建了兩個 Box 物件,一個是 string 型別的,另一個是 number 型別的。

合併接口

如果 聲明同樣名稱的接口,只要在同一個 .ts 文件下,不管離得多遠,接口就會 自動合併,並不會像 type 一樣出現錯誤。不過除非不得已,不然威爾豬自己比較少這麼做,感覺程式碼一多會顯得混亂,看以下範例:

interface IAnimal {
  name: string;
}

interface IAnimal {
  sound: string;
}

const dog: IAnimal = {
  name: "小灰狗",
  sound: "汪汪",
};

上面範例就是聲明了兩個一樣名稱為 IAnimal 的接口,一個有 name 屬性,一個有 sound 屬性,而 IAnimal 型別的 dog 就會自動包含了兩種屬性。


總之,interface 不僅能夠應用於物件,還可以描述函式型別、類別的結構和繼承關係,使用接口的主要目的是:

  • 定義物件的結構和形狀,確保物件符合特定的結構要求

  • 定義函數的型別,包括參數和返回值的結構

  • 提高程式碼的可讀性和可維護性

看到這邊肯定會覺得 type 和 interface 使用上感覺大同小異,那它們有什麼不同或區別呢?又應該什麼時候使用哪一種方式?這我們之後的章節再來討論。


上一篇
類型別名 (Type)
下一篇
Utility 型別 Ⅰ
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言