← 返回题目列表

useLayoutEffect 和 useEffect 有什么区别?

中等 第 21 / 27 题 更新于 2026/07/29
ReactuseLayoutEffectuseEffect渲染

简化版

useEffect 在浏览器绘制后异步执行,适合请求、订阅、日志等不阻塞视觉的副作用;useLayoutEffect 在 DOM 更新后、浏览器绘制前同步执行,适合读取布局并立即修正样式。滥用 useLayoutEffect 会阻塞绘制。

详细版

两者都在提交阶段后执行,但时机不同。

useEffect(() => {
  console.log('绘制后执行')
}, [])

useLayoutEffect(() => {
  const rect = ref.current!.getBoundingClientRect()
  setHeight(rect.height)
}, [])

如果副作用不影响首帧布局,用 useEffect。如果需要在用户看到页面前完成测量和同步修正,例如 Tooltip 定位、虚拟列表初始测量、避免闪烁的布局调整,才考虑 useLayoutEffect

完整版教学

一、区别的核心是是否阻塞浏览器绘制

React 更新 DOM 后,浏览器还要计算样式、布局和绘制。 useLayoutEffect 会在绘制前同步执行,执行期间浏览器不能把新画面显示给用户。 useEffect 会推迟到绘制之后,不挡住首帧。

React render -> commit DOM -> useLayoutEffect -> paint -> useEffect

如果一个 effect 执行 20ms,放在 useLayoutEffect 里可能直接让首帧晚 20ms。 放在 useEffect 里,用户至少能先看到更新后的页面。 所以它不是“高级版 useEffect”,而是更强也更危险的同步钩子。

二、useEffect 适合绝大多数副作用

网络请求、事件订阅、日志上报、手动同步非关键状态,大多不需要阻塞绘制。 用户看到页面后再执行这些工作,体验通常更好。 这也是日常开发优先使用 useEffect 的原因。

useEffect(() => {
  const onResize = () => console.log(window.innerWidth)
  window.addEventListener('resize', onResize)
  return () => window.removeEventListener('resize', onResize)
}, [])
场景推荐
请求接口useEffect
订阅事件useEffect
日志埋点useEffect
首帧前修正位置useLayoutEffect

如果某个副作用晚一点执行不会让用户看到错误画面,就不要用同步布局 effect。 这是性能和体验之间最稳的默认选择。

三、useLayoutEffect 适合读布局再同步写布局

典型例子是 Tooltip。 你需要先渲染一个浮层,读它的宽高和目标元素位置,然后在浏览器绘制前把它放到正确位置。 如果用 useEffect,用户可能先看到浮层出现在错误位置,下一帧才跳过去。

useLayoutEffect(() => {
  const target = targetRef.current!.getBoundingClientRect()
  const tip = tipRef.current!.getBoundingClientRect()
  setStyle({
    left: target.left + target.width / 2 - tip.width / 2,
    top: target.bottom + 8
  })
}, [open])
错误体验:
先画在 0,0 -> effect 后移动 -> 用户看到闪一下

layout effect:
测量并修正 -> 再绘制 -> 用户只看到正确位置

这类场景的关键词是“避免视觉闪烁”。 如果没有这个问题,useLayoutEffect 多半不是必要的。

四、同步执行意味着性能压力更直接

useLayoutEffect 里读取布局再写样式,可能触发浏览器同步布局计算。 如果在列表里有 100 个子组件都做测量,成本会很明显。 尤其在低端设备上,几十毫秒就足以造成卡顿。

// 谨慎:每一项都同步测量
items.map(item => <MeasuredRow key={item.id} item={item} />)

假设每个测量平均 0.3ms,100 项就是 30ms。 这已经超过一帧 16.7ms 的预算。 工程里应尽量批量测量、缓存结果,或把测量限制在可视区域。

五、SSR 场景要额外小心

在服务端没有真实 DOM,也没有布局。 useLayoutEffect 在服务端没有意义,框架通常会给出警告。 如果组件既要 SSR 又要布局测量,可以只在客户端挂载后渲染相关内容,或封装同构安全的 hook。

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

这段写法不是银弹,但表达了边界:服务端无法读取布局,客户端才做同步测量。 如果项目使用 Next.js,这个点很容易被追问。

六、选择时按“用户会不会看到错误中间态”判断

很多副作用都可以晚一点。 真正需要 useLayoutEffect 的,是晚一点就会让用户看到错误画面的操作。 例如测量高度后立即展开、定位浮层、滚动位置恢复、首帧前修正布局。

记忆钩子:useEffect 不挡画面,useLayoutEffect 挡住画面先修布局。

这个判断比死背生命周期更有用。 面试时先说时机,再说场景,最后补性能和 SSR,答案就很完整。

七、常见误区与追问

  • 误区:useLayoutEffect 是 useEffect 的高级替代。 它会阻塞绘制,只应在必须同步测量或修正布局时使用。
  • 误区:请求接口应该放 useLayoutEffect。 请求不会修正首帧布局,放 useEffect 更合适。
  • 误区:useLayoutEffect 一定能提升体验。 如果逻辑耗时,会推迟绘制,反而让页面更卡。
  • 追问:为什么 Tooltip 常用 useLayoutEffect? 它需要先测量目标和浮层尺寸,再在绘制前修正位置,避免闪烁。
  • 追问:SSR 中为什么会有警告? 服务端没有 DOM 和布局,useLayoutEffect 的语义无法执行。
  • 追问:两者清理函数有区别吗? 都可以返回清理函数,主要差别在执行时机。

八、加强记忆

这题按“绘制前后”记。useLayoutEffect 在 DOM 提交后、paint 前同步执行,适合测量布局并防闪;useEffect 在 paint 后执行,适合大多数副作用。能不用同步就不用同步,性能会感谢你。