Generics
泛型(Generics)讓我們可以對類型進行參數化,方便讓 TS 專案中讓定義的方法可以適用於不同的型別。例如:創建一個函式,其 input 和 output 型別一樣
// 如果沒有泛型,則input/output需要指定特定型別
function identity(arg: number): number {
return arg;
}
// 使用泛型,則input/output回傳同一型別
function identity<Type>(arg: Type): Type {
return arg;
}
//執行function時再將型別帶入
let output = identity<string>("myString");
型別參數推斷 Type Argument Inference
TS 通常會根據調用函式時提供的參數型別和回傳值型別,自動推導 T 的型別,這個過程稱作 type argument inference
// 雖然沒有告知 TS function identity<T>(x: T) 的 T 型別為何
// 但 TS 會自動根據帶入函式中參數 x 的型別來推導 T 的型別為 string
let output = identity("myString");
帶有預設值的 Generics
// 沒有給 T 的話,T 預設的型別會是 string
interface WrappedValue<T = string> {
value: T;
}
Generics 限制
在我們使用泛型時,它會被視為「任何(any)」和「所有型別(all types)」。此時。若我們這樣寫會報錯,因為編譯器沒辦法確定 T 是否有對應的 .length 方法:
function logLength<T>(arg: T): T {
// Property 'length' does not exist on type 'T'
console.log(arg.length);
return arg;
}
為了要確保 T 一定帶有 .length 這個方法,我們可以透過定義 interface 搭配 T extends Interface 來的方式來限制 T 能夠使用的方法
interface ContainingLength {
length: number;
}
// T 一定要滿足 ContainingLength interface
function logLength<T extends ContainingLength>(arg: T): T {
console.log(arg.length);
return arg;
}
// 錯誤:Argument of type 'number' is not assignable to parameter of type 'ContainingLength'
// 因為 number 不能滿足有 length 這個 property
const id = logLength(2);
// 正確:由於有滿足 ContainingLength interface 所以可以
const result = logLength({ length: 30 });
// 箭頭函式的話可以寫成這樣
const logLength: <T extends ContainingLength>(arg: T) => T = (arg) => {
console.log(arg.length);
return arg;
};
我們也可以根據另一個 Type 來限制另一個 Type。舉例來說,為了確保 T 這個型別裡有 K 這個屬性時,可以這樣寫:
// K 一定要滿足是 T 的 property
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
// 直接讓 compiler 推斷型別
const value1 = getProperty({ foo: "bar" }, "foo");
// 或者將型別明確給入
interface IPayload {
foo: string;
}
const value2 = getProperty<IPayload, "foo">({ foo: "bar" }, "foo");