---
# System prepended metadata

title: 所謂的 Generics 泛型

---

# 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
