我有一个组件,它用useSelector
侦听Redux存储中的某个值。每当值发生变化时,它都应该重新渲染。不过,当我将两个操作紧接在一起调度时,它会跳过第一个值更改,不会重新渲染组件。
我可以通过将两个调度调用封装在一个setTimout(…, 0)
中来解决这个问题。但这可能是一个肮脏的变通方法,我想知道是什么导致了这种与我的期望不匹配。
在这个CodePen中可以找到一个最小的例子:https://codepen.io/lenzls/pen/GRMYJMz?editors=1011
摘录
function StatusDisplay () {
const comparisonFunc = (newSelectedState, latestSelectedState) => {
const equality = newSelectedState === latestSelectedState
console.log(`[StatusDisplay] — Call comparisonFunc — new: ${newSelectedState} | old: ${latestSelectedState} | equal?: ${equality}`)
return equality
}
const status = useSelector(state => state.status, comparisonFunc)
console.log(`[StatusDisplay] — !!render component — status: ${status}`)
return <div>UpdatTestComp {status}</div>
}
function App () {
const changeState = () => {
console.warn(`PRESSING BUTTON`)
console.log(`Dispatch status = empty — Expecting [StatusDisplay] to rerender…`)
store.dispatch({type: 'CHANGE', payload: 'empty'});
console.log(`Dispatch status = error — Expecting [StatusDisplay] to rerender…`)
store.dispatch({type: 'CHANGE', payload: 'error'});
}
const changeStateAfterTimeout = () => setTimeout(changeState, 0)
return (
<div>
<button onClick={changeState}>dispatch two actions</button>
<button onClick={changeStateAfterTimeout}>dispatch two actions after timeout</button>
<StatusDisplay />
</div>
);
}
示例
页面加载时,初始状态为error
。控制台打印
"Initial status: error"
"[StatusDisplay] — !!render component — status: error"
然后我按下dispatch two actions
按钮。控制台打印
"PRESSING BUTTON"
"Dispatch status = empty — Expecting [StatusDisplay] to rerender…"
"[StatusDisplay] — Call comparisonFunc — new: empty | old: error | equal?: false"
"Dispatch status = error — Expecting [StatusDisplay] to rerender…"
"[StatusDisplay] — Call comparisonFunc — new: error | old: empty | equal?: false"
"[StatusDisplay] — Call comparisonFunc — new: error | old: error | equal?: true"
"[StatusDisplay] — !!render component — status: error"
我们看到useSelector
的比较函数实际上是在状态更改之后(第二次操作之前(立即调用的,但组件仍然没有重新渲染。
如果我按下dispatch two actions after timeout
按钮,控制台将打印
"PRESSING BUTTON"
"Dispatch status = empty — Expecting [StatusDisplay] to rerender…"
"[StatusDisplay] — Call comparisonFunc — new: empty | old: error | equal?: false"
"[StatusDisplay] — Call comparisonFunc — new: empty | old: empty | equal?: true"
"[StatusDisplay] — !!render component — status: empty"
"Dispatch status = error — Expecting [StatusDisplay] to rerender…"
"[StatusDisplay] — Call comparisonFunc — new: error | old: empty | equal?: false"
"[StatusDisplay] — Call comparisonFunc — new: error | old: error | equal?: true"
"[StatusDisplay] — !!render component — status: error"
在这种情况下,组件实际上渲染两次,每次更改status
都渲染一次。无论在哪种情况下,这都是我所期望的。
我假设Redux的dispatch
实际上是同步的,正如这里所描述的,但react Redux中正在进行一些优化。不过,我在文档中找不到有关它的信息。
总的来说,我的实际例子比上面的要间接一点,而且这两个动作并没有从字面上前后改变同一件事。
这是预期行为。React和React Redux将把这些分派批处理在一起,因为它们都发生在同一事件处理程序和同一事件循环中。因此,这将有意只导致使用最终状态进行一次重新渲染。
如果您将处理程序更改为在超时后运行,React 17不会自动将这些更新批处理在一起。但是,React 18将对它们进行批处理。通过在从React-Redux导出的batch()
API中包装两个分派,您可以使用React17获得类似的行为