# 🏅 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)
-->