javascript - 型別化陣列與聯合型別
我經常使用型別陣列,而且我的很多函數實際上應該能處理任何類型的型別陣列(例如,對 Uint8Array 或 Float32Array 進行求和)。有時,我可以僅用簡單的類型聯合來解決,但經常會遇到相同的錯誤。
一個簡單的例子:
type T1 = Uint8Array;
type T2 = Int8Array;
type T3 = Uint8Array | Int8Array;
// No problems here:
const f1 = (arr: T1) => arr.reduce((sum, value) => sum + value);
const f2 = (arr: T2) => arr.reduce((sum, value) => sum + value);
// Does not work:
const f3 = (arr: T3) => arr.reduce((sum, value) => sum + value);
f3 的錯誤是:
Cannot invoke an expression whose type lacks a call signature. Type '
{
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number): number;
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue: number): number;
<U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8Array) => U, initialValue: U): U;
} | {
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number): number;
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue: number): number;
<U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int8Array) => U, initialValue: U): U;
}' has no compatible call signatures.ts(2349)
根據文件:
如果我們有一個具有聯合類型的值,我們只能訪問在聯合中的所有類型中共有的成員。
我在這裡使用 reduce 的方式對所有陣列都是通用的,但我推測問題出在可選的第四個參數(對於 Uint8Array.prototype.reduce 是 Uint8Array,但對於 Int8Array.prototype.reduce 則是 Int8Array)。
有沒有這個問題的簡單解決方法?還是我需要為每個 map、reduce、filter 寫一個泛型實現?
答案#1
在調用函數聯合方面一直存在問題。直到最近,規則是完全不允許調用,但自 3.3(PR)以來,在某些條件下允許調用。你在這裡遇到的最大問題是,如果聯合的成員都有泛型簽名,調用仍然是不允許的。例如,在簡單的數組上,可以調用 forEach(它沒有泛型類型參數),而 reduce 無法調用(因為 string[] 和 number[] 的 reduce 都有泛型類型參數):
declare let o: string[] | number[];
o.forEach((e: number | string) => console.log(e)); // ok
o.reduce((e: number | string, r: number | string) => e + ' ' + r) //err
這確實意味著擁有一個陣列類型的聯合很難使用,並且只允許調用非常少量的方法(大多數陣列方法都有泛型類型參數)。
這也適用於 Uint8Array 和 Int8Array,雖然它們沒有繼承陣列,但擁有大部分相同的方法。
這裡沒有好的解決方案,最簡單的變通方法是將變數斷言為其中一種類型並使用它(假設你不使用 array 回調參數,應該沒問題)
const f3 = (arr: T3) => (arr as Uint8Array).reduce((sum, value, array /* no good inferred to Uint8Array */) => sum + value);
或者退回到你可以調用的其中一個函數
const f4 = (arr: T3) => {
let sum = 0;
(arr as Uint8Array).forEach((val)=> sum + val)
}
答案#2
有一個簡單的解決方法。宣告一個具有共同方法簽名的介面
type T3 = Uint8Array | Int8Array;
interface HasReduce {
reduce(c: (p: number, n: number) => number): number; // common callback signture with 2 arguments
}
function someLogic(arr: HasReduce): number {
return arr.reduce((sum, value) => sum + value);
}
declare var v : T3;
someLogic(v); // OK
所以你可以宣告 HasMap、HasFilter 並將它們結合起來。
