---
# System prepended metadata

title: "\U0001F3C5 Day 8 - 型別別名（type）"
tags: [TypeScript 直播班-2025]

---

# 🏅 Day 8 - 型別別名（type）

## 型別別名（Type Aliases）

`型別別名（Type Aliases）` 允許為任何型別定義一個新名稱。不僅僅是簡單的重新命名，它還允許你建立更複雜、更具表達力的型別結構，尤其是複雜的物件或聯合型別。

### 範例 1: 基本型別別名

```tsx

type StringOrNumber = string | number;

function logMessage(message: StringOrNumber): void {
    console.log(message);
}

logMessage("Hello, TypeScript!");
logMessage(42);

```

在這個範例中，**`StringOrNumber`** 是一個型別別名，代表 **`string | number`** 的聯合型別。

### 範例 2: 物件型別別名

```tsx
type User = {
    name: string;
    age: number;
    isActive: boolean;
};

function printUser(user: User): void {
    console.log(`Name: ${user.name}, Age: ${user.age}, Active: ${user.isActive}`);
}

printUser({ name: "洧杰", age: 18, isActive: true });

```

在這個範例中，**`User`** 是一個型別別名，用於描述一個具有 **`name`**、**`age`** 和 **`isActive`** 屬性的物件。

## 常用情境一：重複使用的複雜型別

### 案例：蝦皮產品

假設你正在開發一個線上商店的應用程式，其中需要處理多種產品資訊。每個產品都有一組共同的屬性，例如名稱、價格和庫存數量。在不同的功能中，你可能需要重複使用這些產品的屬性結構。

### 定義產品型別別名

```tsx

type Product = {
    id: number;
    name: string;
    price: number;
    stock: number;
};

```

**`Product`** 型別別名被用來表示商店中的一個產品。這個型別別名包含了所有產品共用的屬性：**`id`**、**`name`**、**`price`** 和 **`stock`**。

### 範例程式碼
```tsx

type Product = {
    id: number;
    name: string;
    price: number;
    stock: number;
};

// 重要：建立空的產品陣列，有發現型別註釋套用在陣列上，改為 Product 型別別名了嗎？
let products: Product[] = [];

// 定義一個函式來新增產品到陣列中
                            // 需符合 Product type 的型別別名
function addProduct(product: Product) {
    products.push(product);
}

// 使用 addProduct 函式來新增產品
addProduct({ id: 101, name: '手機', price: 29999, stock: 50 });
addProduct({ id: 102, name: '耳機', price: 999, stock: 100 });

// 顯示產品陣列
console.log(products);

```

在這個情境中，`Product` 型別別名被用於多個函式中來代表產品資訊。

而且在需要修改產品屬性結構時，只需要更新 `Product` 型別別名就好，就不用去修改每一處使用到這些屬性的代碼。

這就是使用 `type` 來定義重複使用的複雜型別的優勢所在 :D


## 常用情境二：增加程式碼可讀性

### 案例：UI 元件狀態管理

假如你在開發一個具有多種狀態的 UI 元件，例如一個按鈕，它可以處於不同的狀態，如「正常」、「禁用」、「載入中」等。除了前面有提到的 `enum` 之外，我們也可以使用型別別名來清晰定義這些狀態。

### 定義按鈕狀態的型別別名

首先定義一個型別別名，來表示按鈕的不同狀態：

```tsx=
type ButtonState = 'normal' | 'disabled' | 'loading';

```

這裡 **`ButtonState`** 型別別名包含了按鈕可能的所有狀態。

### 使用按鈕狀態型別別名

接著，我們可以使用這個型別別名來管理 UI 元件的狀態：

```tsx=
function renderButton(state: ButtonState) {
    switch (state) {
        case 'normal':
            console.log('渲染正常狀態的按鈕');
            break;
        case 'disabled':
            console.log('渲染禁用狀態的按鈕');
            break;
        case 'loading':
            console.log('渲染載入狀態的按鈕');
            break;
        default:
            console.log('未知的按鈕狀態');
    }
}

// 測試不同的按鈕狀態
renderButton('normal');
renderButton('disabled');
renderButton('loading');

```

**`renderButton`** 函式根據傳入的 **`ButtonState`** 狀態來決定如何渲染按鈕。這種方式使得狀態管理變得更加清晰和簡單

如果需要增加新的按鈕狀態，只需在 **`ButtonState`** 型別別名中增加新的狀態，並在 **`renderButton`** 函式中加入對應的處理邏輯即可

這樣的型別別名定義還有助於避免錯誤，例如不小心將錯誤的狀態傳遞給按鈕，因為 TypeScript 的型別檢查會在開發、編譯時補捉到這些錯誤。



## 常用情境三：條件型別（Conditional Types）

在 TypeScript 中，**`extends`** 關鍵字用於條件型別的定義中，這是一種特殊的語法，它允許我們根據某個條件來選擇兩種型別之一。

下面來提供範例，條件型別的一般形式是：

```tsx

A extends B ? C : D
```

這裡的意思是，**`A`** 和 **`B`** 是兩種型別，**`C`** 和 **`D`** 也是型別。

如果 **`A`** 可以賦值給 **`B`**，那麼此條件型別的結果是 **`C`** 型別，否則是 **`D`** 型別。

在建立基於條件的型別時（例如，根據某些條件選擇不同的型別），型別別名可以用來定義這類型複雜邏輯。

直接來上案例~

### 案例：基於使用者角色選擇不同的權限型別

假使你正在開發一個系統，其中使用者根據他們的角色（例如說管理員或一般使用者）都擁有不同的權限。我們可以使用條件型別來定義一個型別別名，根據使用者的角色動態選擇相應的權限型別。

### 定義基本型別和條件型別別名

```tsx
// 基本的使用者和管理員權限型別
type UserPermissions = {
    canView: boolean;
    canEdit: boolean;
};

type AdminPermissions = {
    canView: boolean;
    canEdit: boolean;
    canDelete: boolean;
    canCreateUser: boolean;
};

// 角色型別
type UserRole = 'user' | 'admin';

// 條件型別別名
// 如果 UserRole 賦值也是 `admin` 字串，那就是前者的型別 AdminPermissions
// 反之則是 UserPermissions
type Permissions = UserRole extends 'admin' ? AdminPermissions : UserPermissions;

```

### 範例程式碼

```tsx=
function getPermissions(role: UserRole): Permissions {
    if (role === 'admin') {
        return { canView: true, canEdit: true, canDelete: true, canCreateUser: true };
    } else {
        return { canView: true, canEdit: false };
    }
}

// 測試不同角色的權限
const adminPermissions = getPermissions('admin');
const userPermissions = getPermissions('user');

console.log('管理員權限:', adminPermissions);
console.log('一般使用者權限:', userPermissions);

```

**這種方法提供了蠻大的靈活性，允許在不同情境下可以重用相同的邏輯，而不需要重寫型別判斷。這就是條件型別的優勢所在**，很適合用在需要根據某些條件（例如配置選項、用戶角色、環境設定等）





## 單選題

1. 在 TypeScript 中，型別別名（Type Aliases）的使用是為了什麼目的？
A. 為了讓變數名稱更短
B. 為了創建新的型別
C. 為了給已存在的型別一個新名稱，並可用於建立複雜型別結構
D. 僅用於數字和字符串型別

2. 條件型別（Conditional Types）的一般形式是什麼？
A. A extends B ? C : D
B. A && B ? C : D
C. A ? B : C
D. A || B ? C : D


3. 下面哪個型別別名，是宣告為原始資料型別？
A. type Example = 1;
B. type Example = 'string';
C. type Example = string;


## **實作題**

### 圖書館借閱管理系統

你正在開發一個圖書館的借閱管理系統，希望能夠控制維護圖書館的會員、書籍以及借閱的記錄，為了先快速交辦一個初版的系統，你決定先針對這幾個核心部分進行設計，暫時不考慮借閱和還書的日期來簡化整體的設計。

#### 任務描述
1. **定義介面**
    - **`Member`**：包含會員姓名（**`name`**）、會員編號（**`memberId`**）、Email（**`email`**）。
    - **`Book`**：包含書名（**`title`**）、作者（**`author`**）、ISBN（**`isbn`**，字串型別）與是否可借（**`available`**，布林型別）。
    - **`Loan`**：包含會員編號（**`memberId`**）、ISBN（**`isbn`**）。

2. **實作資料結構**
    - 建立 **`members`** 陣列儲存 **`Member`**。
    - 建立 **`books`** 陣列儲存 **`Book`**。
    - 建立 **`loans`** 陣列儲存 **`Loan`**。

3. **開發功能**
    - **`addMember`**：新增會員到 **`members`**。
    - **`addBook`**：新增書籍到 **`books`**。
    - **`checkoutBook`**：會員借書（需檢查會員存在、書存在且可借、未被同會員重複借出）。
    - **`returnBook`**：會員還書（需檢查借閱紀錄存在、還書後書籍 available 應設回 true，並移除該筆 Loan）。
    - **`listMemberLoans`**：列出指定會員所有借閱紀錄。

#### 部分程式碼
```tsx
// TODO: 定義 Member 型別
type Member

// TODO: 定義 Book 介面
type Book

// TODO: 定義 Loan 介面
type Loan

// 資料結構
let members: Member[] = [];
let books: Book[] = [];
let loans: Loan[] = [];

// 功能實作

// 新增會員
function addMember(member: Member): void {
  // TODO: 可加上檢查 memberId 不重複
  members.push(member);
  console.log(`會員已新增：${member.name} (${member.memberId})`);
}

// 新增書籍
function addBook(book: Book): void {
  // TODO: 可加上檢查 isbn 不重複
  books.push(book);
  console.log(`書籍已新增：${book.title} (${book.isbn})`);
}

// 會員借書
function checkoutBook(memberId: string, isbn: string): void {
  // 1) 檢查會員是否存在
  const member = members.find(m => m.memberId === memberId);
  if (!member) {
    throw new Error(`找不到會員：${memberId}`);
  }

  // 2) 檢查書籍是否存在且可借
  const book = books.find(b => b.isbn === isbn);
  if (!book) {
    throw new Error(`找不到書籍 ISBN：${isbn}`);
  }
  if (!book.available) {
    throw new Error(`書籍目前不可借出：${book.title}`);
  }

  // 3) TODO: 檢查是否已被同會員借出（避免重複借同一本）

  // 4) TODO: 新增借閱紀錄 & 設為不可借

  console.log(`借書成功：${member.name} 借閱《${book.title}》`);
}

// 會員還書
function returnBook(memberId: string, isbn: string): void {
  // 1) 找到對應借閱紀錄索引
  const idx = loans.findIndex(l => l.memberId === memberId && l.isbn === isbn);
  if (idx === -1) {
    throw new Error(`找不到借閱紀錄（memberId=${memberId}, isbn=${isbn}）。`);
  }

  const loan = loans[idx];

  // 2) TODO: 刪除借閱紀錄

  // 3) TODO: 找到書籍並將書籍設為可借

  console.log(`還書成功：ISBN=${isbn}，會員=${memberId}`);
}

// 測試操作
addMember({ name: "Alice", memberId: "M001", email: "alice@example.com" });
addBook({ title: "Clean Code", author: "Robert C. Martin", isbn: "978-0132350884", available: true });
addBook({ title: "The Pragmatic Programmer", author: "Andrew Hunt", isbn: "978-0201616224", available: true });

checkoutBook("M001", "978-0132350884");
returnBook("M001", "978-0132350884");

// 再借另一本
checkoutBook("M001", "978-0201616224");
```

<!-- 解答

單選題：C、A、C
實作題：https://codepen.io/zeoxer/pen/XJXwVPL
 
-->
