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 类似的行为