先记住这个答案
React 渲染阶段计算界面,提交阶段把结果应用到宿主环境;浏览器何时把像素绘制出来是另一件事。非交互触发的 Effect 通常会在浏览器先绘制后运行,交互引起的 Effect 则可能在绘制前运行。因此不能靠 useEffect 保证用户已经看见某个画面。必须在绘制前完成的布局测量与校正,可以考虑 useLayoutEffect,但其中的工作会阻塞绘制,应保持必要且短小。
- 提交不等于浏览器已经绘制出像素
- useEffect 不是固定的下一帧回调
- 需要阻止错误布局闪现时评估 useLayoutEffect
为什么“异步执行”不能推出严格的绘制顺序
面试中常把 useEffect 简写成异步、useLayoutEffect 简写成同步,这会省略最关键的参照对象。Effect 的调度与更新来源有关,React 为了让交互结果能被相关系统观察到,可能在浏览器绘制前执行交互触发的 Effect。不能再由一个日志先后顺序推导所有更新的时序。
即使 Effect 已运行,其中安排的 state 更新也不是“用户已经看到新画面”的证明。一个更新可能引出另一次渲染与提交,而浏览器尚未显示中间状态。需要研究某种具体交互时,应结合性能时间线、DOM 变化和绘制记录,不要把 console 顺序直接当成像素显示顺序。
浮层位置闪动时先确认测量需要什么
假设提示框必须先读取自身高度,才能判断应放在目标上方还是下方。如果先按默认位置显示,再在普通 Effect 中测量并更新位置,用户可能短暂看到错误布局。可以在 useLayoutEffect 中完成必要测量与校正,让浏览器绘制前的结果已经符合定位要求。
这种处理并不意味着所有逻辑都应搬进 useLayoutEffect。大型计算、无关请求或日志上报会延长绘制阻塞时间,而且未必需要布局信息。优先考虑是否能用 CSS、已有尺寸或组件结构避免测量;只有需要读取实际布局且不能接受中间画面时,才使用更严格的时机。
同步外部系统与安排视觉反馈分别设计
订阅连接、同步文档标题或维护外部资源,通常不需要阻止浏览器显示已经计算好的页面,可以使用恰当的普通 Effect。若某段逻辑必须等待可见反馈,例如先让进度提示出现再执行昂贵工作,就应分析浏览器调度与工作拆分,而不是仅包进 useEffect 就认为用户已经看到了提示。
服务端渲染时 Effect 不运行,所以不能把 SEO 所需正文或关键初始内容完全寄托于它。布局测量也必须等到客户端具有真实 DOM 后才能进行。对于只能在客户端确定的位置或尺寸,可以提供稳定的初始结构与合适的初始布局,避免水合时依赖不存在的服务端测量结果。
容易答错的地方
- useEffect 永远晚于 paint,所以适合所有延后任务
- 它没有这样的绝对顺序保证,交互触发时可能在绘制前执行。延后昂贵工作还涉及主线程让步和任务拆分,不能把 Effect 当成后台线程。
- 改成 useLayoutEffect 就能修复所有闪烁
- 闪烁也可能来自数据缺失、样式加载或错误状态设计。只有确实需要在绘制前测量并调整布局的部分才适合这样处理,否则可能只是用阻塞掩盖结构问题。
面试官还会怎么问?
useLayoutEffect 里面更新 state 会怎样?
相关更新会在浏览器重新绘制前被处理,以支持测量后立即校正布局;这也意味着它会占用关键显示时段。应避免在里面安排与布局无关的长计算。
渲染被放弃了,那个渲染对应的 Effect 会执行吗?
Effect 与已提交的界面同步,不能把组件函数每执行一次当成 Effect 已建立一次。渲染阶段应保持纯粹,不能提前在函数体内启动本应由同步流程管理的外部工作。
服务端可以用 useEffect 生成页面首屏内容吗?
Effect 不在服务端运行,依赖它才产生的内容不会出现在初始服务端 HTML 中。需要初始可见与可抓取的数据,应通过框架的数据获取与渲染机制准备。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。