# Typescript 泛型
###### tags: `typescript`
最近發現專案中常常有類別或方法旁邊加上:`<b>`的東西,只知道是泛型卻不明白所以,所以就來了解一下ts中的泛型吧!
## 函式使用泛型
在函式後面加上 <Type> 表示動態型別,<Type> 命名也是可以自行定義的,如<List>。只是 <T> 及 <Type>比較通用,代表著<span style="color:#ea748c">傳進來的型別是甚麼,輸出後的型別他就會是傳進來的型別 </span>,以下是簡單的使用範例
```typescript!
function identity<T>(arg: T): T {
return arg;
}
// 調用
let output1 = identity<string>("hello");
let output2 = identity<number>(100);
console.log(output1); // 輸出 hello
console.log(output2); // 輸出 100
function identity2<Type>(arg: Type[]): Type[] {
return arg;
}
let output3 = identity2([1, 2, 3]);
let output4 = identity2(["a", "b", "c"]);
console.log(output3);// [1, 2, 3]
console.log(output4); //["a","b","c"]
```
調用函式時,可將型別傳入函式,可以指定型別`idebtity2<string>("D","E")`傳入型別為string,也可以將型別判斷交給ts的自動推論(type argument inference),當然這是最常見的方式了:
```typescript!
let output5 = identity2<string>(["a", "b", "c"]); //指定泛型回傳型別 `<type>`
let output6 = identity2(["a", "b", "c"]); //inference 自動推論
console.log(output5); // [ 'a', 'b', 'c' ]
console.log(output6); // [ 'a', 'b', 'c' ]
```
### 類別class使用泛型
泛型也可以用於類別的型別定義中:
```typescript!
/** 範例一:: */
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
// 使用泛型類
let box1 = new Box<string>("hello");
let box2 = new Box<number>(100);
console.log(box1.getValue()); // 輸出 hello
console.log(box2.getValue()); // 輸出 100
/** 範例二:: */
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
//限制為 number 型別
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
```
### 使用泛型介面
```typescript!
interface Collection<T> {
add(item: T): void;
remove(item: T): void;
getItems(): T[];
}
class MyCollection<T> implements Collection<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
remove(item: T): void {
let index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
}
getItems(): T[] {
return this.items;
}
}
// 使用泛型接口
let collection1: Collection<number> = new MyCollection<number>();
collection1.add(1);
collection1.add(2);
collection1.add(3);
console.log(collection1.getItems()); // 輸出 [1, 2, 3]
let collection2: Collection<string> = new MyCollection<string>();
collection2.add("hello");
collection2.add("world");
console.log(collection2.getItems()); // 輸出 ["hello", "world"]
```
### 約束泛型
泛型也並非是可以隨意使用的,因為在不了解`<type>`的類型時,依然有可能會有錯誤的情況發生,例如:
```typescript!
function logIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
//error:Property 'length' does not exist on type 'T'.
```
因為`<T>`泛型T中不一定包含屬性`length`,所以錯誤了。因此,我們可以對泛型進行約束:
```typescript!
interface Lengthwise {
length: number;
}
function loggingIdentity2<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity2(3); //error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'. (不符合約束條件interface)
loggingIdentity2({ length: 10, value: 3 });
```
### 在約束泛型中使用類型參數
可以指定受另一個型別約束的參數型別, 如 Key 收到 Type 的型別約束(keyof), 所以 key 參數型別只能是 obj 參數所定義的型別。
如下面例子 getProperty(x, "m")時, 就會報錯提醒沒有 m 參數。
```typescript!
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
let value1 = getProperty(x, "a");
let value2 = getProperty(x, "m"); //error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'. m 不存在於 x 當中的key值
console.log(value1); //1
console.log(value2); //undefined
```
### 泛型介面 Generic Interface
使用含有泛型的介面來定義函式的形狀:
```typescript!
interface GenericIdentity {
<Type>(arg: Type): Type
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentity = identity;
```
可以把泛型引數提前到介面名上:
```typescript!
interface GenericIdentity<T> {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentity<number> = identity;
```
注意: 在使用泛型介面的時候,需要定義泛行的型別。