【问题标题】:Component renders twice on async React context update组件在异步 React 上下文更新时呈现两次
【发布时间】:2020-02-02 07:20:39
【问题描述】:

我有使用 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,它通过采取一系列措施来解决这个问题,但我很好奇它是否可以避免。

【问题讨论】:

    标签: reactjs react-hooks react-context react-state-management react-state


    【解决方案1】:

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

    React 批处理在 React 事件处理程序期间完成的所有 setStates,并在退出其自己的浏览器事件处理程序之前应用它们。

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

    我们可以以某种方式批量更新吗?

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

    【讨论】:

      【解决方案2】:

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

      不过,看起来 React 确实有一个不稳定 (因此不应该使用它) API 用于批量更新ReactDOM.unstable_batchedUpdates。但是,如果将其删除或更改,则使用它可能会在将来引起头痛。但是要回答您的问题“我们能以某种方式批量更新吗?”,是的。

      const asyncInc = async () => {
        await delay(1000);
      
        ReactDOM.unstable_batchedUpdates(() => {
          setCounter(counter + 1);
          setCounter(counter + 1);
        });
      };
      

      【讨论】:

      • 谢谢!实际上,我只是将调度方法更改为采取一系列操作以在一个操作中执行它们,而不是处理不稳定的 API。这正是我期望在这里发生的事情,但不知道有一些实验性的 API 可以处理它。并感谢您的文章!
      猜你喜欢
      • 1970-01-01
      • 2020-09-14
      • 2021-12-21
      • 1970-01-01
      • 2023-01-25
      • 2019-10-15
      • 2022-08-08
      • 2016-06-01
      • 1970-01-01
      相关资源
      最近更新 更多