# TypeScript 所謂的 Generics 泛型
泛型是 TypeScript 中常被使用到的功能,簡單來說它能讓型別變成變數,並依據不同型別來加以實現覆用性。如果有寫過強型別,會知道任何回傳值都需要被加以定義。
TypeScript 特性:
1. 安全性: 泛型允許我們在函數、class 或 interface 中使用類型參數,從而在編譯時實現類型檢查,提供更好的類型安全性。
2. 保留靜態類型: 使用泛型可以保留 TypeScript 的靜態類型功能,讓編譯器能夠檢查和推斷類型,減少類型錯誤。
### 那泛型跟定義 any 的差異在哪呢?
在使用上兩者都能實現型別的不確定性,但其中有很重要的區別,如下:
#### any
1. 彈性極高:可以表示任何類型,可以接受任何值,完全不會進行類型檢查和限制。
2. 不安全性:失去使用 TypeScript 的優勢,導致類型錯誤在運行時才被發現。
#### 泛型(Generics)
1. 提高代碼可重用性。
### 泛型使用方式
我們來時做一個 function 可以回傳傳入的值
```
function inputByNum(arg: number): number {
return arg;
}
let outputNum = inputByNum(10) // 10
```
可以發現傳入參數只能傳入`<number>`,但當我們希望回傳字串時候變成需要寫成兩個 function
```
function inputByNum(arg: number): number {
return arg;
}
function inputByStr(arg: string): string {
return arg;
}
const outputNum = inputByNum(10) // 10
const outputStr = inputByStr('字串') // 字串
```
可以發現這兩個 function 內部其實做得的事情一樣差別在於傳入參數的型別不同限制,這時候泛型就派上場了~把型別抽象化取出,將兩個函式整合成一個:
```
// 在函式後面加上動態型別,名稱可以自由定義,但通常會是以 T / Type 來命名。
function input<T>(arg: T): T {
return arg;
}
const outputNum = input<number>(10) // 10
const outputStr = input<string>('字串') // 字串
```
透過將型別當成變數一樣告訴`<T>`去定義型別就可以達到函式復用性,也可以省略`<>`讓型別自動推導。
### Generic interface 泛型介面
#### interface
它可以用來定義物件型別,常以I作為開頭,主要關注在**該型別的物件能夠做什麼**。
下面例子定義一個函數 logKeyValuePair,它接受類型為 KeyValuePair<T, U> 的參數,其中 T 表示鍵的類型,U 表示值的類型:
```
interface KeyValuePair<T, U> {
key: T;
value: U;
}
function logKeyValuePair<T, U>(pair: KeyValuePair<T, U>): string {
return `Key: ${pair.key} Value: ${pair.value}`
}
const pair1: KeyValuePair<number, string> = { key: 1, value: "hello" };
const pair2: KeyValuePair<string, string> = { key: 'Hello', value: "world" };
logKeyValuePair(pair2) // "Key: Hello Value: world"
```
logKeyValuePair 可以與任何類型的 KeyValuePair 實例一起使用,從而提供靈活性和類型安全性。
### Generic class 泛型類別
```
class Generic<Type> {
value: Type;
add: (x: Type, y: Type) => Type;
constructor(value: Type, add: (x: Type, y: Type) => Type) {
this.value = value;
this.add = add;
}
}
let myGenericNumber = new Generic<number>(0, function(x, y) {
return x + y;
});
console.log(myGenericNumber.value) // 0
let myGenericString = new Generic<string>('Nono', function(x, y) {
return x + y;
});
console.log(myGenericString.add("Hi ", myGenericString.value)) // Hi Nono
```
### Generic Constraints 泛型約束
當我們試著想要去印出參數的 length 時會發生錯誤提示告知 length 不存在於`T`中。
```
function input<T>(arg: T): T {
console.log(arg.length) // Property 'length' does not exist on type 'T'.(2339)
return arg;
}
const outputNum = input<number>(10)
const outputStr = input<string>('字串')
```
由於使用泛型等於讓型別成為任何類型,所以無法明確得知型別定義,以上面例子為例,`<T>` 不一定擁 length method;這時候可以透過創建 interface 進行泛型約束,只允許傳入符合 interface 的變數傳入
```
interface Length {
length: number;
}
function input<T extends Length>(arg: T): T {
console.log(arg.length)
return arg;
}
```
可以發現加上後原本的 error 消失,利用 extends 讓 T 必須符合介面 包含 Length 屬性,這時候如過傳入的參數如果不包含 length 就會發生編譯錯誤:
```
const output = input<number>(3)
// Argument of type 'number' is not assignable to parameter of type 'Length'.(2345)
```
編譯正確:
```
const output1 = input<Length>({length: 3})
```
### Type Alias in Generic
在 Type Alias 中也可以使用泛型的概念,定義了一個 Person 的 type 屬性為`age`型別為`<number>`,這時候如果定義了字串會出錯:
```
type Person = {
age: number;
};
const nono: Person = {
age: 25
};
const lin: Person = {
age: '二十五' // Type 'string' is not assignable to type 'number'.(2322)
};
```
這時候可以利用泛型改寫:
```
type Person<T> = {
age: T;
};
const nono: Person<number> = {
age: 30,
};
const lin: Person<string> = {
age: '三十',
};
```
這邊要注意 type 沒有型別推斷,故需要將型別明確定義才行。
### Generic Parameter Defaults
TypeScript 2.3 新增了對聲明泛型類型參數預設值的支援。
```
function printValue<T = string>(value: T): void {
console.log("value type:", typeof value);
}
printValue("ABC"); // value type: string
printValue(456); // value type: number
printValue<string>("DEF"); // value type: string
printValue<boolean>(true); // value type: boolean
```
雖然類型推斷(type inference)通常可以推斷正確型別,但在某些情況下,仍希望為泛型參數提供預設類型,以確保一致性或處理類型推斷可能無法正確確定類型的情況。
refer:
https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-parameter-defaults