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

为何React Hooks在不同测试场景下出现不一致的stale data问题?

为什么测试一没有出现stale data问题?

组件代码

function App() {
  let [counter, setCounter] = useState(0);
  return <button onClick={() => setCounter(counter + 1)}>{counter}</button>;
}

测试一(可正常运行)

it("should increment a counter", () => {
  const el = document.createElement("div");
  document.body.appendChild(el);
  // 挂载到document.body确保事件正常工作
  ReactDOM.render(<App />, el);
  const button = el.childNodes[0];

  for (let i = 0; i < 3; i++) {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  }

  expect(button.innerHTML).toBe("3");
});

测试二(运行失败)

it("should increment a counter", () => {
  const el = document.createElement("div");
  document.body.appendChild(el);
  // 挂载到document.body确保事件正常工作
  ReactDOM.render(<App />, el);
  const button = el.childNodes[0];

  act(() => {
    for (let i = 0; i < 3; i++) {
      button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    }
  });

  expect(button.innerHTML).toBe(3); // 失败,实际值是"1"!
});

作者提到:若事件处理函数调用间隔过近,可能会使用stale data从而丢失部分增量。但测试一并未出现该问题,为何它不像测试二那样出现stale data?

原因解析

核心差异在于React的更新批次策略和act()对更新时机的控制:

测试一的运行逻辑

在没有act()包裹的情况下,测试环境中的React会在每一次事件触发完成后,立即同步处理状态更新:

  • 第一次点击触发事件处理函数,调用setCounter(0+1),React马上把状态更新为1,DOM同步刷新;
  • 第二次点击时,事件处理函数里的counter已经是最新的1,调用setCounter(1+1)后状态更新为2;
  • 第三次点击同理,最终状态变为3,和预期一致。

测试二的运行逻辑

当用act()包裹整个循环时,React会强制把所有在act()内部触发的更新批量延迟到act()执行完毕后统一处理:

  • 循环里的三次点击事件是同步连续执行的,每一次事件处理函数捕获的counter都是初始值0(状态还没来得及更新),三次调用都是setCounter(0+1);
  • React在批量处理这些更新时,会合并多次传值式的setState调用,最终只把状态设置为1,所以按钮显示1,测试失败。

简单总结:act()让三次更新攒到一起批量处理,导致所有事件处理函数都用了旧的counter值;而测试一中每次更新都即时生效,下一次点击能拿到最新状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:50:57