[TS] Generics(泛型)
keywords: type argument,type parameter
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料。
- Creating Types from Types TypeScript > Handbook > Type Manipulation
- Generics @ TypeScript > Handbook > Type Manipulation
- TypeScript Fundamental v2 @ FrontEnd Master
- What is the syntax for Typescript arrow functions with generics? @ StackOver Flow
TL;DR
- 使用泛型的時候,要留意使用在
function和一般的type、interface時會稍有不同。函式多了 type argument inference,TS 會根據「呼叫函式時」帶入參數的型別(或回傳值的型別),自動推導函式中泛型的型別。
// type
type Dict<T> = {
value: T;
};
// interface
interface WrappedValue<T> {
value: T;
}
// arrow function
const identity = <T>(x: T): T => x;
// function
function identity<T>(x: T): T {
return x;
}
基本語法
Generics(泛型)很重要的一點,就是讓我們寫的方法可以適用在不同的型別,而非只能使用在單一型別。
// https://www.typescriptlang.org/docs/handbook/2/generics.html
// arrow function 能在 .ts 但不能在 .tsx 中使用
// 因爲 TS 會無法清楚區分 <> 指的是 JSX 或 Generics Type
const identity = <T>(x: T) => x;
// identity 這個 function 接收型別為 T 的參數,並回傳型別為 T 的參數
function identity<T>(x: T): T {
return x;
}
備註
因為在 .tsx 中,TS compiler 會無法清楚區分 <> 是指 JSX 或 Generics Type,雖然有些 work around 可以避掉,但最簡單的方式就是「當需要在函式中定義泛型時,就直接使用傳統的 Function Statement」。
Type Argument Inference
一般來說,可以直接使用該函式,而 TS 會根據「呼叫函式時所『帶入參數的型別』、『回傳值的型別』」,自動推導 T 的型別,這個過程稱作 type argument inference:
/* type argument inference */
// 雖然沒有告知 TS function identity<T>(x: T) 的 T 型別為何
// 但 TS 會自動根據帶入函式中參數 x 的型別來推導 T 的型別
// 因爲 x 的
const id = identity('foo');
若有需要的話,也可以在使用帶有泛型的 utility type 後,可以把該函式的型別帶入:
let id = identity<string>('foo');
定義泛型
// type
type Dict<T> = {
value: T;
};
// interface
interface WrappedValue<T> {
value: T;
}
// arrow function
const identity = <T>(x: T): T => x;
// function
function identity<T>(x: T): T {
return x;
}
帶有限制的泛型 Type Parameter with Constraints
透過 extends 的使用,可以建立帶有「限制」的泛型:
interface WrappedValue<T extends string> {
value: T;
}
// ⭕️ T 滿足 string 的型別
const val: WrappedValue<'Aaron' | 'PJ'> = {
value: 'Aaron',
};
// ❌ T 不滿足 string 時會噴錯
// Type 'number' does not satisfy the constraint 'string'.
const val: WrappedValue<number> = {
value: 30,
};
// ❌ 因為沒有給 T 預設值,所以不能留空
// Generic type 'WrappedValue<T>' requires 1 type argument(s).
const val: WrappedValue = {
value: 30,
};
實際的例子像這樣:
// 接收 array: T[] 做為參數,回傳 Dictionary {[k: string]: T}
// 若把 T 的限制 { id: number } 拿掉的話,將會沒辦法確定 T 是帶有 id 的物件
const arrayToDict = <T extends { id: number }>(array: T[]): { [k: string]: T } => {
const out: { [k: string]: T } = {};
array.forEach((val) => {
out[val.id] = val;
});
return out;
};
提示
要了解泛型中為什麼加上限制最快的方式,是把該限制拿掉,然後看 TS 有沒有出現錯誤提示。