# 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; ``` 注意: 在使用泛型介面的時候,需要定義泛行的型別。