【问题标题】:Typed arrays and union types类型化数组和联合类型
【发布时间】:2019-11-14 23:12:19
【问题描述】:

我经常使用类型化数组,而且我的很多函数确实应该能够使用任何类型的类型化数组(例如,对 Uint8ArrayFloat32Array 求和)。有时,我可以只使用一个简单的类型联合,但我经常会遇到同样的错误。

一个简单的例子:

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)

根据docs

如果我们有一个联合类型的值,我们只能访问联合中所有类型共有的成员。

我在这里使用reduce 的方式对所有数组都是通用的,但我推测问题是可选的第四个参数(Uint8Array.prototype.reduceUint8Array,但Int8Array.prototype.reduceInt8Array )。

是否有一个简单的解决方法?还是我需要为mapreducefilter 中的每一个编写一个泛型实现?

【问题讨论】:

  • 好吧有一个丑陋的解决方案(arr as Uint8Array).reduce((a, b) =&gt; a + b);

标签: javascript typescript typescript-typings typed-arrays


【解决方案1】:

有一个简单的解决方法。用通用方法签名声明接口

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 并将它们组合起来。

【讨论】:

  • 无需将回调限制为两个参数(playground)。不过,我认为这就是 OP 所说的想要避免做的事情。不只是reduce,还有mapfiltercopyWithin、...
  • 那么存在维护风险,即必须向接口添加新方法,而不仅仅是能够继承对类型化数组接口的编辑。 (不过,它可能仍然是唯一/最好的解决方案。我希望不是,但是......)
【解决方案2】:

在调用函数联合时一直存在问题。直到最近,规则是根本不允许调用,但由于 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

这确实意味着数组类型的联合很难使用,并且只允许调用非常小的一组方法(大多数数组方法都有泛型类型参数)。

这也适用于Uint8ArrayInt8Array,它们虽然不继承数组但具有大部分相同的方法。

这里没有好的解决方案,最简单的解决方法是将变量断言为其中一种类型并继续使用(假设您不使用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)
} 

【讨论】:

  • 但是forEach 没有泛型类型参数。编辑答案更清楚
  • 所以问题不是回调中array 参数的类型。这两种类型都有一个泛型类型参数,并且该 PR 中的实现不再试图处理这种复杂程度。正如 PR 中所说,“这似乎是一个很好的停止点。” 呵呵。
  • @T.J.Crowder 是的,就是这样。 IMO 我看到的常见用例正是数组的联合,所以 weswigham 停在那里的事实是不幸的(尽管我可以看到合并具有不同约束的通用方法的混乱)
  • 这让我有点发疯。 :-) 我无法用其他类型复制它:pastebin.com/JXnNFQiw(游乐场链接太大)。即使我从lib.es6.d.ts 复制Int8ArrayUint8Array 的整个定义并将它们重命名为FooArrayBarArray,OP 的示例仍然有效。但不适用于Int8ArrayUint8Array。 o_O 我也忍不住要注意,OP 的调用不匹配reduce 的重载与泛型类型参数,因为它只提供了一个参数,它只匹配三个中的第一个重载。
  • @T.J.Crowder 问题是你的两种类型在结构上是相同的,所以联合实际上在检查时崩溃了,所以没有函数联合导致问题。 lib.d.ts 中定义的接口在结构上是相同的,但是lib.es2015.symbol.wellknown.d.ts 中添加的部分实际上使类型不兼容(interface Int8Array { readonly [Symbol.toStringTag]: "Int8Array"; } interface Uint8Array { readonly [Symbol.toStringTag]: "UInt8Array"; }
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-06
  • 1970-01-01
  • 1970-01-01
  • 2017-12-18
  • 2019-02-14
  • 1970-01-01
相关资源
最近更新 更多