TypeScript 的 generic 函式
編寫一個函式,其中輸入的型別與輸出的型別相關,或者兩個輸入的型別以某種方式相關。 讓我們考慮一個返回陣列第一個元素的函式:
function firstElement(arr: any[]) {
return arr[0];
}
這個函式完成了它的工作,但不幸的是返回型別為 any。 如果函式返回陣列元素的型別會更好。
在 TypeScript 中,當我們想要描述兩個值之間的對應關係時,會使用泛型。 我們通過在函式簽名中宣告一個型別引數來做到這一點:
function firstElement<T>(arr: T[]): T { return arr[0]; } const arr: string[] = ['1', '2', '3']; const result = firstElement(arr); console.log(result); const result1:number = firstElement(arr);
TypeScript 的型別推斷 - Type inference
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}
注意,上面程式碼的 Inout,Output 可以被 T, V 替代,但是可讀性不如前者。
Type constrains - 型別約束
我們已經編寫了一些可以處理任何型別值的通用函式。 有時我們想關聯兩個值,但只能對某個值的子集進行操作。 在這種情況下,我們可以使用約束來限制類型引數可以接受的型別種類。
讓我們編寫一個函式,返回兩個值中較長的一個。 為此,我們需要一個長度屬性,它是一個數字。 我們通過編寫 extends 子句將型別引數限制為該型別:
function longest<Type extends { length: number }>(a: Type, b: Type) { if (a.length >= b.length) { return a; } else { return b; } } // longerArray is of type 'number[]' const longerArray = longest([1, 2], [1, 2, 3]); console.log(longerArray); // longerString is of type 'string' const longerString = longest("alice", "bob"); console.log(longerString); // Error! Numbers don't have a 'length' property const notOK = longest(10, 100);
最後一個函式呼叫會出現編譯錯誤,因為 TypeScript 基本型別 number 並不存在名稱為 length 的屬性。
同 Java 相比,TypeScript 的型別約束的強大之處在於,extends 後面緊跟的不需要是 TypeScript 的 built-in type,比如本例裡的:
{
length: number
}
意思是,只要該函式傳入的型別,至少包含型別為 number 的 length 屬性即可。
Specifying Type Arguments
TypeScript 通常可以在泛型呼叫中推斷出預期的型別引數,但並非總是如此。 例如,假設您編寫了一個函式來組合兩個陣列:
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
編譯錯誤:
解決辦法:使用尖括號語法,顯式傳入型別引數:這裡 T = string | number,意思是接收 string 或者 number 型別均可。
編寫 generic 函式的最佳實踐
編寫泛型函式很有趣,而且很容易被型別引數衝昏頭腦。 有太多型別引數或在不需要它們的地方使用約束會使推理不那麼成功,使函式的呼叫者感到沮喪。
最佳實踐1 - Push Type Parameters Down
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
乍一看,兩種方法似乎相同,但 firstElement1 是編寫此函式的更好方法。 它的推斷返回型別是 Type,但 firstElement2 的推斷返回型別是 any,因為 TypeScript 必須使用約束型別解析 arr[0] 表示式,而不是在呼叫期間“等待”解析元素。
最佳實踐 2- Use Fewer Type Parameters
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
我們建立了一個不關聯兩個值的型別引數 Func。 這總是一個危險訊號,因為這意味著想要指定型別引數的呼叫者必須無緣無故地手動指定一個額外的型別引數。 Func 不會做任何事情,只會讓函式更難閱讀和推理!
最佳實踐3 - Type Parameters Should Appear Twice
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
greet("world");
這個例子裡, Str 只出現了一次,因此根本毫無必要。
可以簡寫成:
function greet(s: string) {
console.log("Hello, " + s);
}
請記住,型別引數用於關聯多個值的型別。 如果一個型別引數在函式簽名中只使用一次,它就沒有任何關係。
更多Jerry的原創文章,盡在:"汪子熙":