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