Angular 中充满空值的 Redux 初始状态

Redux initial state full of null values in Angular

我已经在一个项目上工作了大约 10 个月,它基于 Angular 4+ 和 Redux(通过 angular-redux/store)。这个项目基本上是成功的,它从一月份开始投入生产,没有遇到任何严重的问题。

但是...我对 Redux 逐渐感到失望的一个问题我认为至少很烦人,但可能不利于维护:初始状态null价值观,所以,我想找出我做错了什么 - 或者如果这是 Redux 设计的问题 本身 .

如何复制它

这是一个简单而好的例子:https://github.com/marinho/example-app

您只需克隆,npm install 它,然后 运行 npm run ng serve 并在浏览器上加载 localhost:4200。然后转到 Console 并过滤“111”以查看两行:

11111 undefined
11112 null

这是由两个可观察订阅打印的,它们分别在 myUndefinedmyObjects.country 上观察值。第一个是无效密钥(未在全局状态上初始化),第二个是使用 null.

初始化的

这些订阅的更精确位置在这里:https://github.com/marinho/example-app/blob/master/src/app/component.ts#L15

为什么会这样

众所周知,在 Redux 上,必须 configureStore 有一个初始状态。这样的初始状态是一棵大树,其中包含所有可能的键,由它们的缩减器初始化。到初始化时,没有任何操作是 运行,reducer 都没有任何有效载荷要解析,因此在大多数情况下它们默认为 null

到这里为止,标准 Redux/Angular,据我所知。

现在,我的两个订阅(myUndefined$myObjects$)观察到AnonymousSubject个实例,它们是angular-redux/store[=80提供的通道=] 发出在关键路径上更新的新值(即 @select(['myObjects', 'city']) 表示 <AppState>.myObjects.city)。

问题是,随着初始状态的初始化,所有已知状态键上都有新值,但几乎所有的都被分配了null

解决方案还是破解?

一旦有订阅监听这些键中的任何一个,这样的 null 将作为第一个值发出,这使我需要在太多地方进行空检查,例如 .filter(x => x !== null) 或类似的。真丑。

另一种方法是使用 Epics(来自 redux-observables ),但它们必须经过精心设计,并且结果闻起来很复杂脆弱的堆栈不那么可靠。

我的印象是,这不仅是 angular-redux/store 本身的问题,而且实际上是具有全局状态的副作用。

一个简单的解决方案可能是将 angular-redux/store 修复为仅在密钥不再 undefined 时才开始发出值,因此,避免初始化密钥使用 null 并且只是更喜欢根本不初始化它们。但同样,如果这是 Redux 的问题,我不确定这是否是正确的方法。

那么,有没有人有同样的经历或者有一个优雅的解决方案?

在我看来,这是 Redux 固有的。从概念上讲,Redux 是一种架构,它鼓励您将 UI 视为在任何给定时间对整个状态的纯粹计算。您的整个应用程序是一个大型状态机。因此,如果您设置 null 的初始值,则该选择的第一个值将为 null。如果您根本不为字段设置初始值,则相应选择的第一个值将是 undefined.

这样,Redux 在您的应用程序中有点 'flattens time' 并将其分解。只有当前状态和下一步操作。这个基本假设使调试工具如此强大。

我们可以让我们的 Observables 'wait until the first non-nil value' 直到触发,但这正是我个人使用 Redux 来避免的那种时间依赖性。我的感觉是,在 Angular-Redux/store 库中进行此更改可能不是正确的选择,因为其他人可能没有预料到它。我也不确定它会对时间旅行和其他调试工具产生什么影响。

那么在您的情况下,有哪些更简洁的方法来处理这些情况?

一般我会考虑以下其中一项:

1) 确保你的减速器总是有一个初始状态。

这适用于您确实具有合理的密钥前期值的情况。所以对于任何减速器控制state.myUndefined,你做

const INITIAL_VALUE = 'some-reasonable-value';
const myUndefinedReducer = (state = 'some-reasonable-value', action) => {
    // etc.
};

通过这种方式,您可以在 reducer 本身中对默认值进行编码。

2) 选择时处理

这适用于不直接绑定到商店的选择 属性(例如,它们是某些 RxJS 计算的结果)。它也适用于密钥初始值是动态的情况,例如一个 undefined 的值,直到 API 调用将其填入或类似。

正如您所指出的,这基本上归结为在多个地方进行过滤,这可能会很冗长。但是在您的示例中,您可以使用 select$ and a Transformer:

更干净地实现它

一些实用程序文件:

export const skipNil = (obs$: Observable<any>): Observable<any> =>
    obs$.filter(v => v !== null && v !== undefined);

Component.ts:

@select$('myUndefined', skipNil) myUndefined$: Observable<any>;
@select$(['myObjects', 'country'], skipNil) country$: Observable<any>;