---
# System prepended metadata

title: "\U0001F3C5 Day 7 - 介面（interfaces）"
tags: [TypeScript 直播班-2025]

---

# 🏅 Day 7 - 介面（interface）

### 介面 (interface)

在 TypeScript 裡頭，介面（Interface）和 type 的作用類似，能夠用來定義物件的型別。

### 基本範例

```tsx=
interface Student {
    studentId: number;
    name: string;
}
// 正確
let bob: Student = {
    studentId: 101,
    name: 'Bob'
};

// 錯誤，只可指定已知的屬性，且 'Student' 中沒有 'gender'。
let Tom: Student = {
    studentId: 101,
    name: 'Tom',
    gender:'male'  // 會報錯
};

```
這裡定義了一個 **`Student`** 介面，然後建立了一個符合這個介面的物件 **`bob`**。

TypeScript 要求物件必須跟介面的定義完全一致，物件多出或少了介面中的屬性都不行。

### 可選屬性

如果有些屬性是選擇性的，可以在屬性名稱後面加上一個問號 `?`：

```tsx
interface Student {
    studentId: number;
    name: string;
    grade?: number;
}
// 正確
let alice: Student = {
    studentId: 102,
    name: 'Alice',
    grade: 90
};
// 正確
let lily: Student = {
    studentId: 103,
    name: 'Lily'
};

```

### 任意屬性

如果想讓介面可以接受其他任何名稱的屬性，可以這樣寫：

```tsx
interface Student {
    studentId: number;
    name: string;
    grade?: number;
    [propName: string]: any;
}

let john: Student = {
    studentId: 104,
    name: 'John',
    department: 'History'
};

```

用 **`[propName: string]`** 表示除了已定義的屬性外，介面還可以接受任何其他的額外屬性。

### 唯讀屬性

如果某些屬性在建立後就不想讓它改變，可以用 **`readonly`** ：

```tsx
interface Student {
    readonly studentId: number;
    name: string;
}

let mike: Student = {
    studentId: 105,
    name: 'Mike'
};

// mike.studentId = 106; // 這行會報錯，因為 studentId 是唯讀的

```

像是上面 **`studentId`** 被設定為 **`readonly`**，就不能再更改。


## 常用情境

1. **定義物件的形狀（Shape）**：當你需要預先定義一個物件應該包含哪些屬性和方法時，介面是非常合適的選擇。特別是在大型項目或團隊協作中。
2. **強化函式參數的型別檢查**：當你需要函式接受帶有特定屬性的物件時，可以使用介面來定義這些屬性，來提高程式碼的可讀性和可維護性。
3. **定義複雜的資料結構**：當處理複雜的資料結構，例如從 API 取得的大型 JSON 物件時，介面可以幫助你管理和理解這些資料結構的形狀，不用每次都需要通靈。



## 情境一：定義物件的形狀（Shape）

### **範例情境：使用者資訊管理系統**

在這個系統需要處理使用者資訊，像是姓名、年齡和電子郵件地址。為了確保在整個應用程式中一致處理這些資訊，我們可以用介面來定義一個「使用者」的形狀。

### 定義「使用者」介面

```tsx
// 重點：定義使用者的形狀
interface User {
    name: string;
    age: number;
    email: string;
}

function displayUser(user: User) {
    console.log(`Name: ${user.name}, Age: ${user.age}, Email: ${user.email}`);
}

let user1: User = {
    name: "陳大明",
    age: 30,
    email: "damingchen@example.com"
};

displayUser(user1);

```

透過這樣的方式確保在整個應用程式中，所有處理使用者資訊的地方都會遵循同一個結構和型別，藉此提高程式碼的一致性和可靠性。

## 情境二：強化函式參數的型別檢查
### **範例情境：商品庫存管理系統**

一個商品庫存管理系統。我們需要處理各種商品的庫存狀況，包括商品名稱、數量和價格。為了確保函式能夠接收正確格式的商品資訊，我們可以用介面來強化函式參數的型別檢查。

### 定義「商品」介面

```tsx=
interface Product {
    id: string,
    name: string,
    quantity: number,
    price: number
}
                     // 重點：參數在型別註釋，使用 Product 介面
function updateInventory(product: Product, additionalQuantity: number) {
    product.quantity += additionalQuantity;
    console.log(`更新 ${product.name}: ${product.quantity} 品項到庫存`);
}

let newProduct: Product = {
    id: "A105",
    name: "筆記型電腦",
    quantity: 20,
    price: 45000
};
// 新增特定產品庫存量
updateInventory(newProduct, 5);

```

透過使用介面來強化函式參數的型別檢查，確保函式只接受符合特定結構的物件，這樣一來可以減少錯誤，又能提高代碼的可靠性 :D


## 情境三：定義複雜的資料結構

### **範例情境：線上課程平台的課程資訊管理**

假設六角學院正在開發一個線上課程平台，需要處理各種課程的詳細資訊，包括課程名稱、講師資訊、課程內容和學生評價等。由於這些資料結構相對複雜，就適合用介面來定義這些結構。

### 定義相關介面

```tsx=
// 講師介面
interface Instructor {
    name: string;
    expertise: string[];
}
// 課程介面，重要，有發現到這裡用到了 Instructor 介面嗎？
interface Course {
    title: string;
    description: string;
    instructor: Instructor;
    rating: number;
    reviews: string[];
}
// 顯示課程資訊
function displayCourseInfo(course: Course) {
    console.log(`課程名稱: ${course.title}`);
    console.log(`課程描述: ${course.description}`);
    console.log(`講者名稱: ${course.instructor.name}`);
    console.log(`課程評價: ${course.rating}/5`);
}
let programmingCourse: Course = {
    title: "進階 TypeScript 程式設計",
    description: "深入瞭解 TypeScript 的高階特性。",
    instructor: {
        name: "王小明",
        expertise: ["TypeScript", "JavaScript", "軟體工程"]
    },
    rating: 4.5,
    reviews: ["很實用的課程！", "深入淺出，易於理解。"]
};

displayCourseInfo(programmingCourse);

```



像是這範例中，**`Instructor`** 介面定義了講師的姓名 (**`name`**) 和其專長領域 (**`expertise`**)。當 **`Course`** 介面中引用 **`Instructor`** 介面時，就表明每個課程都會有一位講師，並且這位講師的資訊會遵循 **`Instructor`** 介面的結構。

### 這樣的好處是在

1. **模組化和重用性**：通過將 **`Instructor`** 作為一個獨立的介面，我們可以在很多地方重用這個介面。這種模組化方法讓程式碼更加清晰，並減少重複性。
2. **清晰的結構定義**：將講師的資訊封裝在自己的介面中，可以讓 **`Course`** 介面的結構更加清晰。
3. **易於維護和擴充**：如果未來講師的資訊需要添加新的屬性或修改，我們只需更改 **`Instructor`** 介面。這種設計讓整個系統更容易維護和擴充。

## 單選題
1. **在 TypeScript 中，interface 主要用於：**
A. 定義函數的返回值
B. 儲存資料
C. 定義物件的形狀
D. 計算數值
2. **在 TypeScript 的 interface 中，可選屬性是如何表示的？**
A. 使用前綴符號 **`?`**
B. 使用前綴符號 **`#`**
C. 使用後綴符號 **`!`**
D. 使用後綴符號 **`^`**
3. **TypeScript 的 interface 可以用於哪種情況？**
A. 定義一個變數的數值
B. 管理複雜的資料結構
C. 進行數學運算
D. 設定程式的背景顏色

## 實作題

### 醫院病患和預約管理系統

#### 任務描述

1. **定義介面**
    - **`Patient`** 介面：應包含病患的姓名（**`name`**）、年齡（**`age`**）、性別（**`gender`**）和病歷號碼（**`medicalRecordNumber`**）。
    - **`Appointment`** 介面：應包含預約的日期（**`date`**）、時間（**`time`**）、醫生姓名（**`doctorName`**）和病患病歷號碼（**`patientMedicalRecordNumber`**），用來關聯病患的病歷號碼。
2. **實作資料結構**
    - 建立一個 **`patients`** 陣列，用於儲存 **`Patient`** 物件。
    - 建立一個 **`appointments`** 陣列，用於儲存 **`Appointment`** 物件。
3. **開發功能**
    - 實作函式 **`addPatient`** 來添加新病患到 **`patients`** 陣列。
    - 實作函式 **`scheduleAppointment`** 來為存在的病患安排新的預約，並加入到 **`appointments`** 陣列。
    - 實作函式 **`cancelAppointment`** 來取消現有的預約。
    - 實作函式 **`listAppointments`** 來列出特定病患的所有預約。


#### 部分程式碼


```tsx
// 定義 Patient 病患介面
interface Patient {
    name: string;
    age: number;
    gender: string;
    medicalRecordNumber: string;
}

// 定義 Appointment 預約介面
interface Appointment {
    date: string;
    time: string;
    doctorName: string;
    patientMedicalRecordNumber: string;
}

// 病患資料陣列
let patients: Patient[] = [];

// 預約資料陣列
let appointments: Appointment[] = [];

// 實作函式 addPatient
function addPatient(patient: Patient): void {
    patients.push(patient);
}

// 實作函式 scheduleAppointment
function scheduleAppointment(appointment: Appointment): void {
    // 確保病患存在
    const patientExists = patients.some(
        (patient) => patient.medicalRecordNumber === appointment.patientMedicalRecordNumber
    );

    if (!patientExists) {
        throw new Error("病患不存在");
    }

    // TODO: 增加一筆預約到預約列表

}

// TODO: 函式 cancelAppointment

// TODO: 實作函式 listAppointments

// 增加病患
addPatient({ name: "John Doe", age: 30, gender: "Male", medicalRecordNumber: "12345" });

// 安排預約
scheduleAppointment({ date: "2024-01-15", time: "10:00", doctorName: "Dr. Smith", patientMedicalRecordNumber: "12345" });
```



<!-- 解答：  
單選題：C、A、B
實作題：https://codepen.io/zeoxer/pen/ByjeJmd
-->