You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React17中setPageStatus与setDeps为何批量更新?求官方依据

React 17 中 setState 批量更新的特殊场景解析

问题现象

我发现如下现象:

App.tsx

import './App.css'
import useHome from './hooks/useHome'

function App() {
  useHome();
  return <div></div>
}

export default App

src/hooks/useHome.ts

import { useEffect, useState } from "react";

const query = (): Promise<number> =>
  new Promise((resolve) =>
    setTimeout(() => {
      resolve(1);
    }, 1000),
  );

const useHome = () => {
  const [pageStatus, setPageStatus] = useState<string>("loading");
  const [data, setData] = useState<number | null>(null);
  const [nothing , setNothing] = useState(0);
  const [deps, setDeps] = useState(data);

  const queryData = async () => {
    setPageStatus("loading");
    try {
      const pageData = await query();
      setData(pageData); // 触发第2次@render
      setPageStatus("normal"); // 触发第3次@render
      setNothing(1);  // 触发第4次@render
    } catch (e) {
      setPageStatus("error");
    }
  };

  useEffect(() => {
    queryData(); 
  }, []);

  useEffect(() => {
    setDeps(data); // 触发第3次@render 
  }, [data]);

  useEffect(()=> {
    console.log('@batch'); // 仅打印2次,为何React会批量更新setPageStatus与setDeps?
  }, [pageStatus, nothing]);

  console.log("@render"); // 共打印4次

  return {
    deps,
    data,
    pageStatus,
  };
};

export default useHome;

在React17中,由于异步函数特性,setData(pageData)和setPageStatus('normal')不会批量更新,这符合预期(React18中会批量更新)。但我发现setPageStatus('normal')和setDeps(data)会被一起批量更新,请问这是为什么?

解答

在React 17中,同步执行的更新会被批量处理,但异步回调(比如await之后的代码)中的更新默认不会批量处理——不过这里的特殊点在于:setPageStatus("normal")是在await后的异步代码中执行的,而setDeps(data)是在useEffect的回调中执行的,这个useEffect的触发条件是data变化,也就是setData(pageData)导致的渲染完成后触发的。

具体逻辑链如下:

  1. setData(pageData)执行后,触发第2次渲染,渲染完成后React进入useEffect执行阶段,调用依赖data的useEffect回调,执行setDeps(data)。
  2. 与此同时,await query()后的代码setPageStatus("normal")也在同一事件循环中执行。
  3. React会把这两个同步触发的更新(setDeps和setPageStatus)合并成一次渲染(第3次@render),所以这两个更新被批量处理了。

核心原因是:React自身控制的执行上下文(比如useEffect、useLayoutEffect等生命周期钩子)中的更新,无论是否嵌套在异步逻辑中,都会被批量处理。因为这些回调是由React调度执行的,React可以控制更新时机,从而合并批量更新。而await之后的代码本身属于用户异步回调,默认不批量,但如果它和React的生命周期回调在同一事件循环中执行,React会自动合并这些更新。

React官方文档虽未直接针对该场景做详细说明,但批量更新的核心规则是:React会尽可能合并多个状态更新以减少渲染次数,尤其是在React自身调度的执行上下文中的更新,都会被批量处理。

补充验证

你看到@batch仅打印2次的原因:

  • 第一次打印是初始渲染后(pageStatus初始为loading)
  • 第二次打印是第4次渲染后(nothing变为1,pageStatus变为normal)
    第3次渲染是pageStatus和deps同时更新导致的,此时nothing未发生变化,所以不会触发useEffect([pageStatus, nothing])的回调,直到setNothing(1)触发第4次渲染,才会再次打印@batch。

内容的提问来源于stack exchange,提问作者WangYuanFuRong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 12:52:46