Lodash _ forEach 破坏了 TypeScript 中的类型保护

Lodash _forEach breaks typeguard in TypeScript

背景

创建一个变量 notifications 当我没有数据时我希望它为 false,否则是一个对象数组。所以我做了一个联合类型: let notifications: object[] | boolean = []; 很好,除非我做 notifications.push(notification); 我得到了

Property 'push' does not exist on type 'boolean | object[]'.
  Property 'push' does not exist on type 'true'.

对,TS不确定是不是数组。我会让它知道。 notifications = [];同样的错误。进行更多挖掘,意识到守卫工作正常,只有当我将我的 .push 放在 lodash 的 _forEach 中时,我才收到错误。

当前状态:

let notifications: object[] | boolean;

if (noNotifications) {
  notifications = false;
} else {
  notifications = [];
  _forEach(notificationsObj, function (notification, type) { // Culprit
    notification['type'] = type;
    notifications.push(notification); // <-- TS error on push
  });
}
return notifications;

我试过的

一堆不同的类型保护方式。不明白 _forEach 破坏它的原因,所以不确定接下来要尝试什么。

问题

是否可以在 _forEach 内的变量上使用 push?如果可以,怎么做?

TypeScript 对 _forEach 的理解不够好,无法知道 notifications 在运行回调中的代码之前不会被重新分配。由于 notifications 不是 const,TypeScript 认为它 可能 被重新分配。 TypeScript 如何进行基于类型的控制流分析有很多 tradeoffs;类型缩小传播到闭包是困难的。由于您无法真正让 TypeScript 遵循控制流,您有几个选择:


最简单的方法是在执行 push():

时断言 notifications 是一个数组
_forEach(notificationsObj, function(notification, type) { // Culprit
  notification['type'] = type;
  (notifications as object[]).push(notification); // assertion
});

这个断言是告诉编译器不要担心。


为了以少量运行时赋值改组为代价获得更高的类型安全性,引入一个 const 变量,然后将其赋值给 notifications:

const notificationsArray: object[] | boolean = []; // cannot be reassigned
_forEach(notificationsObj, function(notification, type) { // Culprit
  notification['type'] = type;
  notificationsArray.push(notification); // no error
});
notifications = notificationsArray; // assignment works

这里 TypeScript 知道 notificationsArray 永远无法重新分配,因此它的类型在回调中一直保持缩小 object[]。然后你可以将它的值赋给notifications。 (您也可以将 notificationsArray 声明为类型 object[] 并放弃 | boolean。我只是在展示 const 影响缩小。)


希望您理解并且其中一种解决方案适合您。祝你好运!

jcalz 的回答解决了您的具体问题,但我只是想提供一种不同的方法来解决您的问题。通过以函数式风格编写它,您可以完全避免很多这些问题。

import * as _ from 'lodash';

function getNotifications(notificationsObj: {[type: string]: object}): object[] | boolean {
  if (_.isEmpty(notificationsObj)) {
    return false;
  } else {
    // note: lodash map will turn an object into an array
    return _.map(notificationsObj, (notification, type) => {
      return {...notification, type};
    });
  }
}

请注意,与您的原始解决方案相比,我还避免改变原始通知对象。如果您依赖发生的突变,那么您应该将那部分改回来。