# 🏅 Day 11 - 什麼是泛型? ## 回顧陣列設定型別的方式 例如: 1. `number[]` 表示一個數字型別陣列 2. `string[]` 表示一個字串型別的陣列。 ```=tsx let numbers: number[] = [1, 2, 3]; let strings: string[] = ["hello", "world"]; ``` 除此之外,還有哪些定義方式呢? ## 陣列泛型(Array Generic) 此時也可以使用陣列泛型 `Array<元素型別>`,來達成一樣的效果: ```=tsx let numbers: Array<number> = [1, 2, 3]; let strings: Array<string> = ["hello", "world"]; ``` 這兩種方式都有符合一樣的效果,下方我們再寫個範例,主要是提供一個字串陣列,並單純回傳這個字串陣列,兩個效果也是一樣的,只是格式不同而已,一個是 **`Array<string>`** 另一個是 **`string[]`**。 ### 範例一:**`Array<string>`**: ```tsx function processStringArray1(arg: Array<string>): Array<string> { return arg; } let stringArray1: Array<string> = ["apple", "banana", "cherry"]; processStringArray1(stringArray1); // 輸出: ["apple", "banana", "cherry"] ``` ### 範例二:**`string[]`**: ```tsx function processStringArray2(arg: string[]): string[] { return arg; } // 使用這個函式 let stringArray2: string[] = ["apple", "banana", "cherry"]; processStringArray2(stringArray2); // 輸出: ["apple", "banana", "cherry"] ``` ## 開始進入泛型(Generics) 泛型(Generics)是一種在定義函式、介面或類別時,不先明確指定具體的型別,而是在實際使用時,再填入型別的特性。 ```=tsx // `T` 是型別參數(Type Parameter),可代進任意輸入的型別。 function hello<T>(data:T){ console.log(data) } hello<string>("Jack")//Jack,data 參數型別會被代入為 String hello<string>(123) //會出錯,型別錯誤 hello<number>(123)//123,data 參數型別會被代入為 number ``` 上面的範例,我在函式名稱 `hello` 後添加了 `<T>`,裡面的 `T` 是**型別參數(Type Parameter)**,可以用來代入任意型別。 這個 `T` 也可以使用其他名稱,例如 `U`,下面程式碼也具備相同功能 ```tsx= //型別參數改為 U function hello<U>(data:U){ console.log(data) } hello<string>("Jack")//Jack,data 參數型別會被代入為 String hello<string>(123) //會出錯,型別錯誤 hello<number>(123)//123,data 參數型別會被代入為 number ``` 不過通常約定俗成主要會使用 `T` 當作型別參數的原因是 `T` 是 type(型別) 縮寫的關係 ### 泛型函式 return 當然這個型別參數,也能應用在 `return` 上 ```=tsx // return 也用到了型別參數 function hello<T>(data:T): T{ return data; } hello<string>("Jack")//回傳 Jack hello<string>(123) //會出錯,型別錯誤 hello<number>(123)//回傳 123 ``` ### 可以有多個型別參數 ```tsx= function callFun<T, U>(e1: T, e2: U): [U, T] { return [e2, e1]; } // 推薦:可以使用型別推論寫法 const a = callFun("world", 123); console.log(a); // [123, "world"] // 也可以使用型別註釋 const b = callFun<string,number>("world", 123); console.log(b); // [123, "world"] // 故意出錯的型別註釋 const c = callFun<string,string>("world", 123); console.log(c); // [123, "world"] ``` ## 問題來了,如果是這樣呢? ```js= function processStringArray(strings: string[]): string[] { // ... 一些對字串陣列的操作 return strings; } function processNumberArray(numbers: number[]): number[] { // ... 一些對數字陣列的操作 return numbers; } ``` 看起來兩個函式有一個共通之處,那就是函式參數、return 的型別都一樣,有辦法透過泛型優化嗎? ### **重構為泛型函式** 我們可以將這些函式重構為一個泛型函式,能夠接受任何型別的陣列。泛型 **`T`** 代表陣列中的元素型別。 ```tsx function processArray<T>(arr: T[]): T[] { return arr; } let strAry = ["apple", "banana", "cherry"]; let processedStrings = processArray<string>(strAry); let numberAry = [1, 2, 3]; let processedNumbers = processArray<number>(numberAry); ``` 在這個重構後的範例中,**`processArray`** 函式使用泛型 **`T`** 來表示陣列元素的型別。當你呼叫這個函式時,可以明確指定這個型別,如 **`processArray<string>`** 或 **`processArray<number>`**。 這樣你就能夠用同一個函式來處理多種型別的陣列,藉此來提升代碼的簡潔性。 ## 實作題 ### 1. 建立泛型字串陣列 #### 函式名稱 `createGenericStringArray` #### 輸入 兩個字串 `str1` 和 `str2`。 #### 輸出 一個泛型陣列 `Array<string>`,包含 `str1` 和 `str2`。 #### 範例 輸入`"hello"` 和 `"world"`,函式回傳陣列為 `["hello", "world"]`。 #### 部分程式碼 ```tsx= function createGenericStringArray(str1: <設定型別>, str2: <設定型別>): <設定回傳型別> { return <回傳格式>; } // 使用這個函式 const stringArray = createGenericStringArray("hello", "world"); console.log(stringArray); // 輸出: ["hello", "world"] ``` ### **2. 泛型函式** 練習泛型 `<T>` 用法 #### **函式名稱** **`identity`** #### **輸入** 一個型別參數 **`value`**。 #### **輸出** 直接回傳輸入的 **`value`**。 #### **範例** 輸入一個數字 **`5`**,函式回傳 **`5`**;輸入一個字串 **`"hello"`**,函式回傳 **`"hello"`**。 #### **部分程式碼** ```tsx= // 補上型別參數 function identity(value):? { return value; } // 使用這個函式 const number = identity(5); console.log(number); // 輸出: 5 const string = identity("hello"); console.log(string); // 輸出: "hello" ``` ### **3. 使用泛型處理不同型別的物件** #### **函式名稱** **`processArray`** #### **輸入** - 一個泛型陣列 **`array: Array<T>`**,其中 **`T`** 可以是 **`string`**、**`number`** 或 **`boolean`**。 - 一個泛型函式 **`process: (item: T) => T`**,用於對陣列中的每個元素進行處理。 #### **輸出** 一個經過處理的泛型陣列 **`Array<T>`**,包含經過函式 **`process`** 處理過的所有元素。 #### **範例** 1. 輸入字串陣列 **`["apple", "banana"]`** 和一個將每個字串轉為大寫的函式,函式回傳陣列為 **`["APPLE", "BANANA"]`**。 2. 輸入數字陣列 **`[1, 2, 3]`** 和一個將每個數字加倍的函式,函式回傳陣列為 **`[2, 4, 6]`**。 3. 輸入布林陣列 **`[true, false, true]`** 和一個將每個布林值取反的函式,函式回傳陣列為 **`[false, true, false]`**。 #### **部分程式碼** ```tsx= function processArray(array, process){ return ??? } function processString(str: string): string{ return ??? } function processNumber(num: number): number { return ??? } function processBoolean(bool: boolean): boolean { return ??? } // 使用這個函式 const processedStrings = processArray(["apple", "banana"], processString); const proceedingNums = processArray([1, 2, 3], processNumber) const proceedingBooleans = processArray([true, false, true], processBoolean) console.log(processedStrings); // 輸出: ["APPLE", "BANANA"] console.log(proceedingNums); // 輸出: [2, 4, 6] console.log(proceedingBooleans); // 輸出: [false, true, false] ``` <!-- 解答: 1. function createGenericStringArray(str1: string, str2: string): Array<string> { return [str1, str2]; } 2. function identity<T>(value: T): T { return value; } 3. function processArray<T extends string | number | boolean>(array: T[], process: (i: T) => T): T[] { return array.map(item => process(item)) } function processString(str: string): string{ return str.toLocaleUpperCase() } function processNumber(num: number): number { return num * 2 } function processBoolean(bool: boolean): boolean { return !bool } const processedStrings = processArray<string>(["apple", "banana"], processString); const proceedingNums = processArray<number>([1, 2, 3], processNumber) const proceedingBooleans = processArray<boolean>([true, false, true], processBoolean) -->