# 🏅 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,並貼至底下回報就算完成了喔! 解答位置請參考下圖(需打開程式碼的部分觀看) ![](https://i.imgur.com/vftL5i0.png) <!-- 解答: --> 回報區 --- | 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)|