【问题标题】:Typescript 3.0.1 can't understand nested reduce(s)Typescript 3.0.1 无法理解嵌套的 reduce(s)
【发布时间】:2019-02-03 10:17:08
【问题描述】:

所以我有这段代码可以找到 x 子数组的所有可能组合:

const optionGroups: string[][] = [['Beer', 'Not beer'], ['1', '2', '11']];

const combinations = optionGroups.reduce((opt1, opt2) => {
  return opt1.reduce((acc, option1) => {
    return acc.concat(opt2.map(option2 => {
      return (<string[]>[]).concat(option1, option2);
    }));
  }, []);
});

return combinations; // <-- This is not a string[]

然而 Typescript 认为返回类型是 string[],而实际上它是 string[][]

输出为[['1', 'Beer'], ['2', 'Beer']...]

我终其一生都无法弄清楚如何修复这种类型。你可以自己试试sn-p看看。

即使转换它也会产生错误:

类型“string[]”无法转换为类型“string[][]”。类型 'string' 不能与类型 'string[]' 比较。

任何指针?

【问题讨论】:

    标签: typescript types reduce


    【解决方案1】:

    使用外部 reduce 将 optionGroups 解构为两个元素很奇怪,这也是问题的根源,因为当您在没有初始状态的情况下执行 reduce 时,假定状态与输入的一个元素,即string[]。外部回调实际上返回string[][],但是不匹配没有被捕获,因为内部reduce 的初始状态[] 的类型被扩大到any[],而没有给出任何隐含错误;见this issue。启用strictNullChecks 将禁用这种糟糕的扩展形式并给您预期的类型错误。

    【讨论】:

    • 最终我认为您可能是对的:它是以一种花哨的方式编写的。我会尝试对其进行重组。
    【解决方案2】:

    查看在这种情况下使用的Array.prototype.reduce 的类型定义(第一次调用它):

    reduce(callbackfn: (prev: T, curr: T) => T): T;
    

    T 是数组元素类型。由于您的呼叫在string[][]Array&lt;string[]&gt; 上运行,因此Tstring[]。所以reduce调用的返回值也必须是string[]。所以从这里的类型来看,一切似乎都是正确的。

    但这显然不是您运行代码时发生的情况。返回值是一个实际的string[][]。当您调试代码时,您会看到类型实际上会在运行时发生变化。

    原因是reduce 在不传递初始值时如何工作:

    如果没有提供初始值,将使用数组中的第一个元素。

    所以opt1 的第一个值将是optionGroupsstring[] 的第一个元素。然后,内部代码运行后,结果实际上是string[][]。您可以通过在内部明确类型来轻松确认这一点:

    const combinations = optionGroups.reduce((opt1, opt2) => {
      const result = opt1.reduce((acc, option1) => {
        return acc.concat(opt2.map(option2 => {
          return (<string[]>[]).concat(option1, option2);
        }));
      }, <string[][]>[]);
      return <any>result;
    });
    

    所以现在的问题是resultstring[][],但外部reduce 仍然假定先前值的string[](因为它的静态类型)。因此,您将不得不为 reduce 使用不同的类型定义,允许先前值和当前值使用不同的类型:

    reduce<U>(callbackfn: (prev: U, curr: T) => U, initialValue: U): U;
    

    不幸的是,这里的初始值是强制性的,所以你必须通过它。你不能在这里传递null 甚至是一个空数组,因为这会破坏你的逻辑,所以你必须稍微调整你的逻辑来以不同的方式处理第一次迭代。比如这样:

    const combinations = optionGroups.reduce<string[][]>((opt1, opt2) => {
      if (opt1 === null) {
        return opt2.map(option2 => [option2]);
      }
    
      return opt1.reduce((acc, option1) => {
        return acc.concat(opt2.map(option2 => {
          return (<string[]>[]).concat(option1, option2);
        }));
      }, <string[][]>[]);
    }, null);
    

    【讨论】:

    • 优秀的答案,但这似乎打破了逻辑并给出以下运行时错误:TypeError: Cannot read property 'reduce' of null
    • 这显然是因为我们试图在新初始化的累加器上执行.reduce,然后它将为空。我正在尝试重组查询。
    • 哦,对不起。我希望通过nullundefined 会触发“将第一个元素作为初始值”行为,但似乎reduce 实现实际上是查看arguments 数组而不是这个。我想我最后忘记测试了。你将不得不改变你的逻辑(或强制类型):/
    • 很好,伙计。我最终强制类型。不知道我应该怎么做这个问题。可能无法回答。
    • 我确实编辑了我的答案以提供一种实际工作的方式,并且还可以使用正确的类型进行编译。所以也许这是解决这个问题的可行解决方案。
    猜你喜欢
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2018-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多