React-Redux 不会使用两个背靠背操作重新渲染组件
React-Redux does not re-render component with two back-to-back actions
我有一个组件,它使用 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
实际上是同步的,如 here 所述,但是 react-redux 正在进行一些优化。不过我在文档中找不到有关它的信息。
总的来说,我的实际例子比上面的更间接一点,这两个动作并不是字面意义上的背靠背改变同一件事。
这是预期的行为。 React 和 React-Redux 会将这些分派批处理在一起,因为它们都发生在同一个事件处理程序和同一个事件循环中。因此,这将有意仅导致使用最终状态的单个重新渲染。
如果您在超时后将处理程序更改为 运行,React 17 不会 自动将这些更新批处理在一起。但是,React 18 将 对它们进行批处理。通过将两个分派包装在从 React-Redux
导出的 batch()
API 中,您可以获得与 React 17 类似的行为
我有一个组件,它使用 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
实际上是同步的,如 here 所述,但是 react-redux 正在进行一些优化。不过我在文档中找不到有关它的信息。
总的来说,我的实际例子比上面的更间接一点,这两个动作并不是字面意义上的背靠背改变同一件事。
这是预期的行为。 React 和 React-Redux 会将这些分派批处理在一起,因为它们都发生在同一个事件处理程序和同一个事件循环中。因此,这将有意仅导致使用最终状态的单个重新渲染。
如果您在超时后将处理程序更改为 运行,React 17 不会 自动将这些更新批处理在一起。但是,React 18 将 对它们进行批处理。通过将两个分派包装在从 React-Redux
导出的batch()
API 中,您可以获得与 React 17 类似的行为