我已经在一个项目上工作了大约 10 个月,该项目基于 Angular 4+ 和 Redux(通过 angular-redux/store )。该项目基本上是成功的,自一月份以来就投入生产,没有遇到任何严重问题。
但是...我对 Redux 的一个问题逐渐感到失望,我认为这个问题至少很烦人,但可能不利于维护: null
的初始状态值,所以,我想找出我做错了什么 - 或者这是否是 Redux 设计本身的问题。
如何重现它
这是一个简单且很好的示例:https://github.com/marinho/example-app
你只需克隆,npm install
它,然后运行 npm run ng serve
并在浏览器上加载localhost:4200。然后转到控制台并过滤“111”以查看两行:
11111 undefined
11112 null
这是由两个可观察订阅打印的,它们分别监视 myUndefined
上的值和myObjects.country
。第一个是无效 key (未在全局状态下初始化),第二个是用 null
初始化的。 .
这些订阅的更精确位置在这里:https://github.com/marinho/example-app/blob/master/src/app/component.ts#L15
为什么会发生
众所周知,在 Redux 上,必须 configureStore
具有初始状态。这样的初始状态是一棵大树,其中包含由其 reducer 初始化的所有可能的键。到初始化时,没有运行任何操作,两个 reducer 都没有任何要解析的有效负载,因此它们默认为 null
在大多数情况下。
到目前为止,标准 Redux/Angular,据我所知。
现在,我的两个订阅( myUndefined$
和 myObjects$
)观察 AnonymousSubject
实例,这是 angular-redux/store 提供的 channel ,用于发出在关键路径上更新的新值(即 @select(['myObjects', 'city'])
表示 <AppState>.myObjects.city
)。
问题是,随着初始状态的初始化,所有已知状态键上都有新值,但几乎所有这些值都被分配了 null
.
解决方案还是黑客?
一旦有订阅监听这些 key 中的任何一个,例如 null
将作为第一个值发出,这使我需要空检查,例如 .filter(x => x !== null)
或类似的地方太多了。实在是太丑了。
另一种选择是使用 Epics (来自 redux-observables ),但它们必须设计良好,结果闻起来既脆弱又不那么 -可靠的堆栈。
我的印象是,这不仅仅是 angular-redux/store 本身的问题,而且实际上是全局状态的副作用。
一个简单的解决方案可能是将 angular-redux/store 修复为仅在 key 不是 undefined
时才开始发出值。再说了,避免使用 null
初始化 key 并且只是不想初始化它们。但同样,如果这是 Redux 的问题,我不确定这是否是正确的方法。
那么,有没有人遇到同样的情况或者有一个优雅的解决方案?
最佳答案
在我看来,这是 Redux 固有的。从概念上讲,Redux 是一种架构,它鼓励您将 UI 视为任何给定时间整个状态的纯粹计算。您的整个应用程序是一台大型状态机。因此,如果您将初始值设置为 null
,则该选择的第一个值将为 null
。如果您根本没有为字段设置初始值,则相应选择的第一个值将为未定义
。
这样,Redux 就会在您的应用中“拉平时间”并将其分解出来。只有当前状态和下一步操作。这一基本假设使得调试工具如此强大。
我们可以让我们的 Observables '等到第一个非零值'直到触发,但这正是我个人使用 Redux 来避免的那种时间依赖性。我的感觉是,考虑到其他人可能没有预料到,在 Angular-Redux/store 库中进行此更改可能不是正确的选择。我也不确定它会对时间旅行和其他调试工具产生什么影响。
那么,就您的情况而言,有哪些更简洁的方法来处理这些情况?
通常我会考虑以下其中一项:
1)确保您的 reducer 始终具有初始状态。
这适用于您实际上拥有合理的 key 预值的情况。因此,对于任何 reducer 控制state.myUndefined
,你都可以这样做
const INITIAL_VALUE = 'some-reasonable-value';
const myUndefinedReducer = (state = 'some-reasonable-value', action) => {
// etc.
};
通过这种方式,您可以在 reducer 本身中对默认值进行编码。
2)选择时处理
这适用于不直接与存储属性相关的选择(例如,它们是某些 RxJS 计算的结果)。它也适用于 key 的初始值是动态的情况,例如一个未定义
的值,直到 API 调用填充它或类似的值。
正如您所指出的,这本质上可以归结为在多个地方进行过滤,这可能很冗长。但是在您的示例中,您可以使用 select$
更干净地实现它。和一个Transformer
:
一些实用程序文件:
export const skipNil = (obs$: Observable<any>): Observable<any> =>
obs$.filter(v => v !== null && v !== undefined);
组件.ts:
@select$('myUndefined', skipNil) myUndefined$: Observable<any>;
@select$(['myObjects', 'country'], skipNil) country$: Observable<any>;
关于angular - Redux 初始状态在 Angular 中充满空值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44714653/