# 🏅 Day 13 - type 泛型
泛型也很常用在 type 型別別名上,如以下範例:
```tsx=
// 代入 Passthrough<T> 裡面的 T 為什麼樣的型別
// 賦值後的 T 就會成為該型別
type Passthrough<T> = T;
// 使用 Passthrough 定義不同型別的變數
let numberValue: Passthrough<number> = 42;
// 引發錯誤,型別衝突
let numberValue2: Passthrough<number> = "42";
let stringValue: Passthrough<string> = "Hello, world!";
let booleanValue: Passthrough<boolean> = true;
let arrayValue: Passthrough<number[]> = [1, 2, 3, 4, 5];
// 使用這些變數
console.log(numberValue); // 輸出: 42
console.log(stringValue); // 輸出: "Hello, world!"
console.log(booleanValue); // 輸出: true
console.log(arrayValue); // 輸出: [1, 2, 3, 4, 5]
```
當使用型別別名泛型時,TypeScript 不會自動去做型別推斷。所以需要在使用時,明確指定型別參數,例如: **`<number>`** 或 **`<string>`**。
## **情境一:身高範例**
通常情況下,我們可能會這樣定義一個表示人的型別,其中包含身高屬性,並設定為 **`number`** 型別:
```tsx=
type Person = {
height: number;
};
const mike: Person = {
height: 180,
};
```
以這例子中,**`mike`** 的身高被設定為一個數字 180。
### **使用泛型的時機**
有可能在專案中,會同時需要用數字,或中文字例如「一百八十公分」來呈現,此時就需要比較靈活的型別定義。這樣泛型就派上用場了。
我們可以這樣定義一個泛型型別名:
```tsx=
type FlexiblePerson<T> = {
height: T;
};
const mike1: FlexiblePerson<number> = {
height: 180,
};
const mike2: FlexiblePerson<string> = {
height: '一百八十公分',
};
```
在 **`mike1`** 的例子中,身高是以數字形式表示的,而在 **`mike2`** 中,身高則是以字串來呈現。
## 情境二:水果物件表
以下是設計一個儲存商品列表資訊的功能。想要用 **`GenericValObj`** 來建立一個物件,該物件將以商品名稱作為 `key`,並以商品價格作為值。
```tsx=
// 新增泛型物件型別
// 屬性 key 必須為字串形式
type GenericValObj<T> = {
[key: string]: T;
}
// 定義 productPrices 儲存商品價格,value 為 number 型別
const productPrices: GenericValObj<number> = {
"apple": 1.99,
"banana": 0.99,
"cherry": 2.49,
};
// 新增更多商品價格
productPrices["grape"] = 1.79;
// productPrices["orange"] = "三美元"; // 會出錯,因為泛型設定的型別參數為 number
// 定義一個 productDescriptions 物件來儲存商品描述,value 為 string 型別
const productDescriptions: GenericValObj<string> = {
"apple": "一個美味的水果",
"banana": "一個黃色的水果",
"cherry": "一個比較小又紅的水果",
};
// 新增更多商品描述
productDescriptions["grape"] = "A small purple fruit";
```
## **情境三:API response 包裝**
如果你正在開發一個應用程式,需要與多個不同的 API 進行交互。這些 API 回傳不同型別的數據
例如:
1. 一些回傳數字類型的數據(如使用者的年齡)
2. 另一些則回傳字符串類型的數據(如使用者的姓名)
如果想要用統一方法來包裝這些 API response,可以這樣寫:
```tsx
type ApiResponse<T> = {
data: T;
status: string;
}
// 使用範例
const ageResponse: ApiResponse<number> = { data: 30, status: "success" };
const nameResponse: ApiResponse<string> = { data: "Alice", status: "success" };
```
如果資料會需要管理 response 回傳成功或失敗的話,可以這樣寫:
```tsx=
// 定義 User 介面
interface User {
name: string;
age: number;
}
// 定義 ApiResponse 泛型型別
type ApiResponse<T> = {
success: boolean;
message: string | null;
data: T | null;
}
// 使用 User 介面的 ApiResponse
const userResponse: ApiResponse<User> = {
success: true,
message: null,
data: { name: "Alice", age: 30 }
};
// 用於失敗的 ApiResponse,沒資料
const errorResponse: ApiResponse<null> = {
success: false,
message: "Error retrieving data",
data: null
};
```
## **情境四:網頁版本的 MP3 播放器**
假設你正在開發一個網頁版的 MP3 播放器。這個播放器需要許多不同的設定選項,例如音量控制、播放模式(例如隨機播放)、使用者設定等。
來試著用泛型來進行規劃:
```tsx=
// 定義設定選項的 type 泛型
type ConfigOption<T> = {
key: string;
value: T;
description: string;
category: string;
}
// 使用範例
const volumeConfig: ConfigOption<number> = {
key: "volume",
value: 75,
description: "控制播放器的音量大小",
category: "音效設定"
};
const shuffleModeConfig: ConfigOption<boolean> = {
key: "shuffleMode",
value: true,
description: "啟用/停用隨機播放模式",
category: "播放控制"
};
const usernameConfig: ConfigOption<string> = {
key: "username",
value: "user123",
description: "用戶的顯示名稱",
category: "用戶設定"
};
```
## 開發題
### 題目一: 資料庫重構優化
請嘗試將
`UserEntry` 和 `ProductEntry`重構,合併成一個自訂的 `type`,並使用泛型來進行優化
```tsx=
// 定義 UserEntry 和 ProductEntry 型別
type UserEntry = {
id: string;
data: {
name: string;
age: number;
};
}
type ProductEntry = {
id: string;
data: {
name: string;
price: number;
};
}
// 建立 UserEntry 和 ProductEntry 的函式
function createUserEntry(id: string, data: { name: string; age: number; }): UserEntry {
return { id, data };
}
function createProductEntry(id: string, data: { name: string; price: number; }): ProductEntry {
return { id, data };
}
// 使用函式來建立 UserEntry 和 ProductEntry
const userEntry = createUserEntry("user1", { name: "John Doe", age: 30 });
const productEntry = createProductEntry("product1", { name: "Apple iPhone 13", price: 799 });
```
### 題目二:旅館訂房網
請嘗試將
`RoomOperationResult` 和 `CustomerOperationResult`重構,合併成一個自訂的`type` ,並使用泛型來進行優化
```tsx=
// 定義 Room 型別別名
type Room = {
id: string;
type: string;
price: number;
available: boolean;
}
// 定義 Customer 型別別名
type Customer = {
id: string;
name: string;
}
// 定義 RoomOperationResult 和 CustomerOperationResult 型別別名
type RoomOperationResult = {
success: boolean;
data: Room;
message: string;
}
type CustomerOperationResult = {
success: boolean;
data: Customer;
message: string;
}
// 建立一個 Customer 陣列來儲存客戶資訊
let customers: Customer[] = [];
// 定義一個函式,該函式接收一個 Room 並回傳一個 RoomOperationResult
function bookRoom(room: Room): RoomOperationResult {
// 檢查房間是否可用
if (room.available) {
// 預訂房間,將 available 屬性設為 false
room.available = false;
const result: RoomOperationResult = {
success: true,
data: room,
message: '預訂成功'
};
return result;
} else {
const result: RoomOperationResult = {
success: false,
data: room,
message: '房間已被預訂'
};
return result;
}
}
// 定義一個函式,該函式接收一個 Room 並回傳一個 RoomOperationResult
function cancelRoom(room: Room): RoomOperationResult {
// 取消房間,將 available 屬性設為 true
room.available = true;
const result: RoomOperationResult = {
success: true,
data: room,
message: '取消預訂成功'
};
return result;
}
// 定義一個函式,該函式接收一個 Customer 並回傳一個 CustomerOperationResult
function addCustomer(customer: Customer): CustomerOperationResult {
// 將客戶添加到客戶陣列中
customers.push(customer);
const result: CustomerOperationResult = {
success: true,
data: customer,
message: '新增客戶成功'
};
return result;
}
// 建立一個 Room
const room: Room = { id: '1', type: '單人房', price: 1000, available: true };
// 使用 bookRoom 函式來預訂房間
const bookResult = bookRoom(room);
console.log(bookResult); // 輸出:{ success: true, data: { id: '1', type: '單人房', price: 1000, available: false }, message: '預訂成功' }
// 使用 cancelRoom 函式來取消房間
const cancelResult = cancelRoom(room);
console.log(cancelResult); // 輸出:{ success: true, data: { id: '1', type: '單人房', price: 1000, available: true }, message: '取消預訂成功' }
// 建立一個 Customer
const customer: Customer = { id: '1', name: 'John Doe' };
// 使用 addCustomer 函式來添加客戶
const addCustomerResult = addCustomer(customer);
console.log(addCustomerResult); // 輸出:{ success: true, data: { id: '1', name: 'John Doe' }, message: '新增客戶成功' }
console.log(customers); // 輸出:[{ id: '1', name: 'John Doe' }]
```
## 回報流程
將答案寫在 CodePen,並貼至底下回報就算完成了喔!
解答位置請參考下圖(需打開程式碼的部分觀看)

<!-- 解答:
-->
回報區
---
| Discord | CodePen / 答案 |
|:-------------:|:----------------------------------------------------------------:|
|洧杰|[Codepen](https://codepen.io/hexschool/pen/poYgYqW?editors=1010)|
|苡安|[Codepen](https://codepen.io/yi-an-yang/pen/ExMvMNQ)|
|展誠|[CodePen](https://codepen.io/hedgehogkucc/pen/LYajavv?editors=1010)|
|hannahpun|[CodePen](https://codepen.io/hannahpun/pen/abMyxWg?editors=0011)|
|Andy|[CodePen](https://codepen.io/qdandy38/pen/BabdEdd?editors=0012)|
|clairechang|[Notion](https://claire-chang.notion.site/Day-13-type-5084ef5556b14f1aa968208ded11c9f6)|
|HsienLu|[CodePen](https://codepen.io/Hsienlu/pen/RwdZVQB)|
|Starr|[CodePen](https://codepen.io/StarrZhong/pen/XWGaLyY)|
|hiYifang|[HackMD](https://hackmd.io/@gPeowpvtQX2Om6AmD-s3xw/r1dPCijta)|
|LinaChen|[HackMD](https://codepen.io/LinaChen/pen/GRevexR)|
|雙魚|[CodePen](https://codepen.io/emiarcak/pen/abMLbxQ?editors=1012)|
|Henrt_Wu|[Codepen](https://codepen.io/hekman1122/pen/RwdLNoa)|
|Alyce|[Codepen](https://codepen.io/alycehwy/pen/oNVGNRR?editors=0010)|
|Mi|[Codepen](https://codepen.io/Mi-Jou-Hsieh/pen/rNRGNKJ?editors=0011)|
|hannahTW|[Codepen](https://codepen.io/hangineer/pen/LYajzzz?editors=0011)|
|jasperlu005|[Codepen](https://codepen.io/uzzakuyr-the-reactor/pen/OJqxMbW?editors=1011)|
|Bryan Chu|[CodePen](https://codepen.io/bryanchu10/pen/NWJaNao)|
|Otis|[CodePen](https://codepen.io/humming74/pen/oNVGzZN?editors=1011)|
|精靈|[CodePen](https://codepen.io/justafairy/pen/xxBXExM)|
|YC|[HackMD](https://hackmd.io/SKoJd3EsTlitnjzCx4Rarg)|
|77_0411|[CodePen](https://codepen.io/chung-chi/pen/XWGezMa?editors=0011)|
|JC|[Codepen](https://codepen.io/jcsamoyed/pen/RwdLxNN?editors=0012)
|Kai|[Codepen](https://codepen.io/kaiyuncheng-the-styleful/pen/ZEPXrbd?editors=0011)
|deedee1215|[Codepen](https://codepen.io/diddy032/pen/rNRGpEE)|
|wendy_.li|[HACKMD](https://hackmd.io/PcmFgqZwRd-4Ep3-LgK5_Q)
|yunhung|[Codepen](https://codepen.io/ahung888/pen/vYPebBg?editors=0011)
|Amberhh| [codepen](https://codepen.io/Amberhh/pen/mdoqerQ?editors=0011)|
|銀光菇| [codepen](https://codepen.io/genesynthesis/pen/eYXeZMY)|
|wei|[codePen](https://codepen.io/jweeei/pen/poYWObp?editors=1012)|
|Lisa|[codePen](https://codepen.io/lisaha/pen/YzgEvyW?editors=1011)|
|翰毅|[codePen](https://codepen.io/yzuigtdw-the-animator/pen/NWJwrYq)|
|rikku1756|[CodePen](https://codepen.io/rikkubook/pen/MWxrpLV?editors=1112)|
|神奇海螺| [CodePen](https://codepen.io/ksz54213/pen/GReyGWr)|
|連小艾|[CodePen](https://codepen.io/bolaslien/pen/YzgYoPQ?editors=0012)|
|BonnieChan|[CodePen](https://codepen.io/Bonnie-chan-the-bold/pen/OJqQJgo?editors=0012)|
|erwin阿瀚|[CodePen](https://codepen.io/yohey03518/pen/wvOymzx)|
|Snorlax|[HackMD](https://hackmd.io/@snorlaxpock/SkMBjXK9T)|
|薏慈|[CodePen](https://codepen.io/its_wang/pen/wvOjZQb)|
|皓皓|[HackMD](https://hackmd.io/@cutecat8110/BJEWBt0F6)|
|leave3310|[HackMD](https://codepen.io/leave3310-the-looper/pen/rNRqeGx?editors=0010)|
|Tori|[HackMD](https://hackmd.io/OAdkiOH-S_WkD-LF6IONVA?view)|
|我是泇吟|[CodePen](https://codepen.io/kljuqbxs/pen/poXmqJm)|