# 🏅 Day 8 - 型別別名(type)
我們在前七天,介紹了許多 TypeScript 的型別,例如元組、聯合、列舉、介面等等
**那麼我們可以為這些型別,自訂型別名稱嗎**?答案是可以的,此時`型別別名(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 元件,例如一個按鈕,它可以處於不同的狀態,如「正常」、「禁用」、「載入中」等。我們也可以使用型別別名來清晰定義這些狀態。
### 定義按鈕狀態的型別別名
首先定義一個型別別名,來表示按鈕的不同狀態:
```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;
## **開發題:圖書租借管理系統**
### **任務解說:**
真實世界遇到的事情,其實遠遠比 Dcard、PTT 上的軟體工程師上鬼故事還要來得可怕
你現在就遇到了,當初圖書館的技術長信誓旦旦和你說,它們系統有導入 TypeScript
結果正式報到後你才發現,接手的是毫無可讀性的 JavaScript 程式碼,你很勉強得看出來,某段程式邏輯大概是在做圖書館的租借功能系統
你嘆了口氣後,決定先寫辭職信...不對,決定先暖暖身,一步步將 JS 改成 TypeScript
此時你可以開始嘗試:
1. 梳理資料結構,先從定義型別,將資料格式一一拆成型別別名(Type Aliases)~
2. 各函式的 if else 巢狀裡面太可怕
3. 租借、歸還邏輯好像有機會將共用的抽離出來
```jsx
function rentBook(books, bookId, userId) {
// 租借圖書
const book = books.find((b) => b.id === bookId);
if (book && !book.isBorrowed) {
book.isBorrowed = true;
book.borrowedBy = userId;
console.log(`成功租借圖書:${book.title}`);
return true;
} else {
console.log(`無法租借圖書:${book.title}`);
return false;
}
}
function returnBook(books, bookId) {
// 歸還圖書
const book = books.find((b) => b.id === bookId);
if (book && book.isBorrowed) {
book.isBorrowed = false;
book.borrowedBy = null;
console.log(`已歸還圖書:${book.title}`);
return true;
} else {
console.log(`無法歸還圖書:${book.title}`);
return false;
}
}
const libraryBooks = [
{ id: 1, title: "JavaScript 程式設計", isBorrowed: false, borrowedBy: null },
{ id: 2, title: "TypeScript 實戰", isBorrowed: false, borrowedBy: null },
{ id: 3, title: "React 開發入門", isBorrowed: false, borrowedBy: null },
];
// 執行租借、歸還邏輯
rentBook(libraryBooks, 2, "user123");
returnBook(libraryBooks, 1);
```
## 回報流程
將答案寫在 CodePen,並貼至底下回報就算完成了喔!
解答位置請參考下圖(需打開程式碼的部分觀看)

<!-- 解答:
單選題目:C、A、C
開發題:https://codepen.io/hexschool/pen/KKENjjP
-->
回報區
---
| Discord | CodePen / 答案 |
|:-------------:|:----------------------------------------------------------------:|
|洧杰|[Codepen](https://codepen.io/hexschool/pen/poYgYqW?editors=1010)|
|神奇海螺|[CodePen](https://codepen.io/ksz54213/pen/OJqpNKW)|
|苡安|[CodePen](https://codepen.io/yi-an-yang/pen/JjzWKjQ)|
|HsienLu|[CodePen](https://codepen.io/Hsienlu/pen/GReWqzy?editors=1111)|
|BonnieChan|[CodePen](https://codepen.io/Bonnie-chan-the-bold/pen/qBvrNZB)|
|77_0411|[CodePen](https://codepen.io/chung-chi/pen/mdoWrqG?editors=1010)|
|YC|[HackMD](https://hackmd.io/SKoJd3EsTlitnjzCx4Rarg?view)|
|ZS|[HackMD](https://codepen.io/irishuang/pen/wvOJzyb?editors=0010)|
|rikku1756|[CodePen](https://codepen.io/rikkubook/pen/eYXvdEZ?editors=1012)|
|Mi|[CodePen](https://codepen.io/Mi-Jou-Hsieh/pen/MWxpjmw?editors=1011)|
|Jack|[CodePen](https://codepen.io/lj787448952/pen/OJqpXwQ)|
|hiYifang|[HackMD](https://hackmd.io/@gPeowpvtQX2Om6AmD-s3xw/SkhePfMKa)|
|LinaChen|[HackMD](https://codepen.io/LinaChen/pen/abMJZzr)|
|m_m|[CodePen](https://codepen.io/minnn7716/pen/MWxpJgX)|
|hannahpun|[CodePen](https://codepen.io/hannahpun/pen/VwRppyX?editors=1011)|
|clairechang|[Notion](https://claire-chang.notion.site/Day-8-type-bc6ea98b420c4a848c2fcd2799f19f17)|
|Henry_Wu|[Codepen](https://codepen.io/hekman1122/pen/LYaWxqR?editors=0012)|
|Bryan Chu|[CodePen](https://codepen.io/bryanchu10/pen/gOEmXQX)|
|jasperlu005|[Codepen](https://codepen.io/uzzakuyr-the-reactor/pen/QWopOGm?editors=1011)|
|Lisa|[Codepen](https://codepen.io/lisaha/pen/KKEWyNe?editors=1012)|
|deedee1215|[CodePen](https://codepen.io/diddy032/pen/XWGMzWy)|
|hannahTW|[CodePen](https://codepen.io/hangineer/pen/YzgZxOj)|
|wendy_.li|[HACKMD](https://hackmd.io/PcmFgqZwRd-4Ep3-LgK5_Q)|
|Starr|[CodePen](https://codepen.io/StarrZhong/pen/rNRyvyx)|
|Kai|[CodePen](https://codepen.io/kaiyuncheng-the-styleful/pen/rNRyjxo?editors=0011)|
|銀光菇|[CodePen](https://codepen.io/genesynthesis/pen/QWopzwz)|
|Amberhh |[codepen](https://codepen.io/Amberhh/pen/XWGMoMX?editors=1011) |
|展誠|[Codepen](https://codepen.io/hedgehogkucc/pen/PoLpXbB?editors=1012)|
|連小艾|[Codepen](https://codepen.io/bolaslien/pen/vYPxvWR?editors=1011)|
|NiuNiu|[Stackblitz](https://stackblitz.com/edit/typescript-kduzd3?file=index.html,index.ts)|
|Otis|[Codepen](https://codepen.io/humming74/pen/BabRoXr?editors=1012)|
|Mia Tsai|[CodePen](https://codepen.io/Mianzi/pen/mdomVOp)|
|yunhung|[CodePen](https://codepen.io/ahung888/pen/qBvmZyQ?editors=0010)|
|Teddy|[CodePen](https://codepen.io/TaideLi/pen/bGZWeWr)|
|翰毅|[CodePen](https://codepen.io/yzuigtdw-the-animator/pen/dyrWZzP?editors=1111)|
|JC|[Codepen](https://codepen.io/jcsamoyed/pen/YzgVYxr?editors=0012)
|wei|[CodePen](https://codepen.io/jweeei/pen/vYPmVQw?editors=1010)|
|薏慈|[CodePen](https://codepen.io/its_wang/pen/wvOewKb)|
|erwin阿瀚|[CodePen](https://codepen.io/yohey03518/pen/XWGgrza)|
|皓皓|[HackMD](https://hackmd.io/@cutecat8110/rkTHZ8sKp)|
|精靈|[CodePen](https://codepen.io/justafairy/pen/zYbzJKg)|
|shan13|[CodePen](https://codepen.io/yishan13-tsai/pen/wvOervN)|
|Farmer Lin|[CodePen](https://codepen.io/wfwqcjbj-the-solid/pen/abMVZVV)|
|Snorlax|[HackMD](https://hackmd.io/@snorlaxpock/Hk3FGhjFp)|
|Rochel|[Codepen](https://codepen.io/rochelwang1205/pen/YzgaNqp?editors=1012)|
|leave3310|[Codepen](https://codepen.io/leave3310-the-looper/pen/poYVqRM?editors=0010)|
|Nick Lin|[Codepen](https://codepen.io/NickLinP/pen/XWGqQgd)|
|Tori|[Hackmd](https://hackmd.io/OAdkiOH-S_WkD-LF6IONVA?view)|
|我是泇吟|[Codepen](https://codepen.io/kljuqbxs/pen/zYVQEdj)|