# 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