这里的问题是,一次reducers的一个小变化会导致选择器重新计算整个派生输出,结果整个React UI也会更新.我的纯组件不起作用.这很慢.
典型示例:我的数据的第一部分来自服务器,基本上是不可变的.第二部分由客户端维护,并使用redux操作进行变更.它们由单独的减速器维护.
我使用选择器将两个部分合并到一个记录列表中,然后传递给React组件.但显然,当我在其中一个对象中更改单个内容时,将重新生成整个列表并创建新的Records实例. UI完全重新渲染.
显然每次运行选择器并不是完全有效但仍然相当快,我愿意做出这种交易(因为它确实使代码更简单,更清晰).问题是实际渲染很慢.
我需要做的是将新选择器输出与旧选择器输出深度合并,因为Immutable.js库足够聪明,不会在没有任何更改时创建新实例.但由于选择器是无法访问先前输出的简单功能,我想这是不可能的.
我认为我目前的做法是错误的,我想听听其他想法.
可能的方法是在这种情况下摆脱重新选择并将逻辑移动到reducers的层次结构中,该层次结构将使用增量更新来维持所需的状态.
原始选择器处理得很好的挑战之一是最终信息是从以任意顺序传递的许多部分编译而来的.如果我决定逐步在Reducer中构建最终信息,我必须确保计算所有可能的场景(信息块可能到达的所有可能的顺序)并定义所有可能状态之间的转换.然而,通过重新选择,我可以简单地拿走我现在拥有的东西并从中取出一些东西.
为了保持这个功能,我决定将选择器逻辑移动到包装父减速器中.
好吧,让我们说我有三个减速器,A,B和C,以及相应的选择器.每个处理一条信息.该部分可以从服务器加载,也可以来自客户端的用户.这将是我原来的选择器:
const makeFinalState(a,b,c) => (new List(a)).map(item => new MyRecord({ ...item,...(b[item.id] || {}),...(c[item.id] || {}) }); export const finalSelector = createSelector( [selectorA,selectorB,selectorC],(a,c) => makeFinalState(a,c,));
(这不是实际的代码,但我希望它有意义.请注意,无论各个reducer的内容可用的顺序如何,选择器最终都会生成正确的输出.)
我希望我的问题现在很明确.如果任何这些reducer的内容发生变化,则从头开始重新计算选择器,生成所有记录的全新实例,最终导致React组件的完全重新呈现.
我目前的解决方案看起来很简单
export default function finalReducer(state = new Map(),action) { state = state .update('a',a => aReducer(a,action)) .update('b',b => bReducer(b,action)) .update('c',c => cReducer(c,action)); switch (action.type) { case HEAVY_ACTION_AFFECTING_A: case HEAVY_ACTION_AFFECTING_B: case HEAVY_ACTION_AFFECTING_C: return state.update('final',final => (final || new List()).mergeDeep( makeFinalState(state.get('a'),state.get('b'),state.get('c'))); case LIGHT_ACTION_AFFECTING_C: const update = makeSmallIncrementalUpdate(state,action.payload); return state.update('final',final => (final || new List()).mergeDeep(update)) } } export const finalSelector = state => state.final;
核心思想是:
>如果发生了重大事件(即我从服务器获得了大量数据),我将重建整个派生状态.
>如果发生了一些小的事情(即用户选择了一个项目),我只是在原始的reducer和包装父减速器中进行快速增量更改(存在一定的重复性,但是必须实现一致性和良好的性能) .
与选择器版本的主要区别在于我总是将新状态与旧状态合并. Immutable.js库非常智能,如果它们的内容完全相同,则不会用新的Record实例替换旧的Record实例.因此保留了原始实例,因此不会重新呈现相应的纯组件.
显然,深度合并是一项代价高昂的操作,因此这对于非常大的数据集不起作用.但事实是,与React重新渲染和DOM操作相比,这种操作仍然很快.因此,这种方法可以在性能和代码可读性/简洁性之间做出很好的折衷.
最后的注意事项:如果不是单独处理那些轻量级动作,那么这种方法基本上等同于用纯组件的shouldComponentUpdate方法中的deepEqual替换shallowEqual.