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};
});
}
}
请注意,与您的原始解决方案相比,我还避免改变原始通知对象。如果您依赖发生的突变,那么您应该将那部分改回来。
背景
创建一个变量 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};
});
}
}
请注意,与您的原始解决方案相比,我还避免改变原始通知对象。如果您依赖发生的突变,那么您应该将那部分改回来。