React 中 flushSync 强制同步更新
React 中的 flushSync:强制同步更新
在 React 18 中,默认的批量更新机制让状态更新变得异步,从而提升性能。但有时我们需要立即读取更新后的 DOM,此时 flushSync 就派上了用场。本教程将带你理解它的行为、使用场景以及背后的原理。
为什么需要 flushSync?
React 18 引入了自动批量更新,无论事件处理程序、setTimeout、Promise 还是原生事件,多个状态更新都会被合并成一次渲染。这虽然减少了重渲染次数,但也意味着你无法在状态更新后立刻拿到最新的 DOM 结果。
function handleClick() {
setCount(c => c + 1);
// 这里读取 DOM,仍然是旧的 count 值
console.log(domRef.current.textContent); // 旧值
}
flushSync 的作用就是强制在回调函数执行期间立即同步执行状态更新以及对应的 DOM 更新,让代码在回调结束时就能读取到最新 DOM。
flushSync 的基本用法
flushSync 接收一个回调函数,回调中的状态更新会被同步处理。它由 react-dom 提供:
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// 此时 DOM 已更新到最新值
console.log(domRef.current.textContent); // 新值
}
⚠️ 注意:
flushSync只能包裹状态更新,不能包含副作用或任意代码。滥用会破坏 React 的优化机制,导致不必要的性能问题。
实际应用场景
1. 需要同步读取 DOM 信息
典型的场景:在状态更新后立即滚动到某个新添加的元素,或测量元素尺寸。
const listRef = useRef(null);
function addItem() {
flushSync(() => {
setItems(prev => [...prev, newItem]);
});
// 立即滚动到新增项位置
listRef.current.lastChild.scrollIntoView();
}
2. 与第三方非 React 库集成
某些库(如地图、图表)依赖同步的 DOM 变更。使用 flushSync 可以确保 React 状态更新后,DOM 立即就绪再调用库方法。
function updateMarkerPosition(pos) {
flushSync(() => {
setPosition(pos);
});
externalMap.updateMarker(pos); // 此时 DOM 已同步
}
3. 处理浏览器默认行为的竞态条件
在阻止事件默认行为前需要同步更新状态,避免事件处理提前读取未更新的状态。
function handleBeforeInput(e) {
flushSync(() => {
setDraft(e.target.value);
});
if (draft.length > 100) {
e.preventDefault(); // 依赖最新 draft 长度
}
}
多个状态更新与嵌套 flushSync
一个 flushSync 回调中可以包含多个状态更新,它们都会被同步执行并合并为一次提交(commit)。但如果 flushSync 嵌套使用,只有最外层的 flushSync 才会触发真正的同步 DOM 刷新,内层的调用会与它合并。
flushSync(() => {
setA(1);
flushSync(() => {
setB(2); // 这里不会单独提交
});
setC(3);
});
// 一次 DOM 刷新,包含 A、B、C 的所有变更
实践建议:尽量避免嵌套
flushSync,保持代码简洁。
性能影响与注意事项
- 性能代价:强制同步更新会中断浏览器的异步渲染优化,可能导致卡顿。只在必要时使用,不要作为“获取最新状态”的捷径。
- 错误边界:
flushSync内部如果发生错误,仍然会被最近的错误边界捕获,但回调中未被同步执行的更新可能会被丢弃,请确保错误处理的健壮性。 - 和并发模式的冲突:
flushSync是脱离并发特性(如 Suspense、startTransition)的“逃生舱”,它会强制 React 进入同步渲染模式,无法被中断或复用。
与旧版 API 的区别
React 17 及之前版本的批量更新仅在事件处理程序中生效,而在 setTimeout、原生事件等场景下,状态更新是同步的。React 18 统一为异步批量更新,flushSync 作为手动恢复同步行为的官方 API 出现,取代了早期常见的 ReactDOM.flushSync(不稳定 API)以及 unstable_batchedUpdates 的逆向操作。
小结
flushSync是 React 18 中用于强制同步 DOM 更新的逃生舱。- 适用于需要立即读取最新 DOM 或集成第三方同步代码的场景。
- 滥用会降低性能,建议优先使用 React 的声明式模式。
- 使用前确保真的无法通过
useEffect或useLayoutEffect等标准方式解决。
掌握 flushSync 能让你在复杂交互中更精细地控制更新时机,但请务必牢记:非必要,不逃离 React 的批量更新机制。