为何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
相关产品推荐
相关产品推荐

