组件在异步 React 上下文更新时呈现两次

Component renders twice on async React context update

我有一个使用 React 上下文管理状态的 React 应用程序。我创建了简单的 counter incrementation reproduction.

有两种情况。一个用于存储状态,第二个用于调度。这是取自 article.

的模式

状态上下文只存储一个数字,并且只有一个动作可以在此状态上调用:

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "inc": {
      return state + 1;
    }
  }
}

我还有两个用于同步和异步递增值的辅助函数:

async function asyncInc(dispatch: React.Dispatch<Action>) {
  await delay(1000);
  dispatch({ type: "inc" });
  dispatch({ type: "inc" });
}

function syncInc(dispatch: React.Dispatch<Action>) {
  dispatch({ type: "inc" });
  dispatch({ type: "inc" });
}

下面是如何在组件中使用它:

const counter = useCounterState();
const dispatch = useCounterDispatch();

return (
  <React.Fragment>
    <button onClick={() => asyncInc(dispatch)}>async increment</button>
    <button onClick={() => syncInc(dispatch)}>sync increment</button>
    <div>{counter}</div>
  </React.Fragment>
);

现在,当我单击 sync increment 按钮时,一切都会按预期进行。它将调用 inc 操作两次,将计数器递增 2 并仅执行一次组件重新渲染。

当我点击 async increment 按钮时,它会先等待一秒钟,然后执行两次 inc 操作,但它会重新渲染组件两次。

您必须打开控制台才能查看渲染组件的日志。

我有点明白这是为什么了。当它是一个同步操作时,一切都发生在组件渲染期间,所以它会先操作状态,然后再渲染。当它是一个异步操作时,它会首先渲染组件,一秒钟后它会更新状态一次触发重新渲染,它会在第二次触发下一次重新渲染时更新。

那么是否可以只执行一次重新渲染来执行异步状态更新?在同一个复制品中,有一个类似的例子,但使用 React.useState 也有同样的问题。

能不能,我们莫名其妙的批量更新?或者我必须创建另一个操作来一次对状态执行多个操作?

我还创建了 this reproduction 通过采取一系列操作来解决这个问题,但我很好奇是否可以避免它。

除非这会导致性能问题,否则我建议不要担心额外的渲染。我发现 this post about fixing slow renders before worrying about re-renders 对这个主题很有帮助。

然而,看起来 React 确实有一个 不稳定的 (所以不应该使用它) API批处理更新 ReactDOM.unstable_batchedUpdates。但是,如果它被删除或更改,使用它可能会在将来引起麻烦。但是要回答你的问题"can we somehow batch updates?",是的。

const asyncInc = async () => {
  await delay(1000);

  ReactDOM.unstable_batchedUpdates(() => {
    setCounter(counter + 1);
    setCounter(counter + 1);
  });
};

基本上,您看到的 syncInc() 的点击事件。因此,您只会看到它渲染一次。

React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.

对于您的 asyncInc(),它超出了事件处理程序的范围(由于 async),因此预计您会得到两次重新呈现(即不批处理状态更新) .

Can, we somehow batch updates?

是的,React 可以 batch updates 在异步函数中。