为什么你需要 useLayoutEffect?深入理解同步副作用

为什么你需要useLayoutEffect?深入理解同步副作用

在 React 的开发中,我们经常使用 useEffect 来处理副作用(如数据获取、订阅、手动 DOM 操作等)。然而,在某些特定的场景下,我们需要在 DOM 更新之后浏览器绘制之前 立即执行一些同步操作,这时就需要使用另一个 Hook ------ useLayoutEffect

一、useLayoutEffect 是什么?

useLayoutEffect 是 React 提供的一个 Hook,用于在组件渲染完成后执行副作用逻辑,但它会在浏览器重新绘制屏幕之前同步执行。这意味这你可以在页面未渲染之前就获取某个DOM节点的状态。

它的用法与 useEffect 几乎完全一致:

jsx 复制代码
useLayoutEffect(() => {
  // 同步执行的副作用逻辑
}, [依赖项]);

不同之处在于执行时机:

  • useEffect:异步执行,在浏览器完成绘制后执行。
  • useLayoutEffect:同步执行,在 DOM 更新后、页面绘制前执行。

二、为什么需要 useLayoutEffect

React 的设计理念是异步和非阻塞的,大多数副作用都可以延迟到绘制之后执行。但在以下几种情况下,必须确保副作用在绘制前完成

  1. 需要读取或修改 DOM 布局信息(如尺寸、位置)

    例如你希望根据某个元素的实际大小来调整布局,如果使用 useEffect,可能会导致视觉上的闪烁或重绘抖动。这是因为useEffect是在页面渲染之后执行的,此时页面已经完成渲染并呈现给用户,这个时候useEffect执行修改样式的操作就会导致样式的快速变化,页面进行再一次的回流重绘从而造成闪烁或抖动

    jsx 复制代码
    useLayoutEffect(() => {
      const height = ref.current?.offsetHeight;
      if (height > 100) {
        setExpanded(false);
      }
    }, []);
  2. 避免视觉抖动(Layout Thrashing)

    如果你在多个异步副作用中反复读写 DOM 尺寸,可能导致多次强制同步布局(Forced Synchronous Layout),影响性能和用户体验。

    useLayoutEffect 可以确保所有计算在绘制前完成,避免这种问题。

三、useLayoutEffectuseEffect 的区别总结

特性 useEffect useLayoutEffect
执行时机 渲染完成后异步执行(绘制后) 渲染完成后同步执行(绘制前)
是否阻塞绘制 是(可能影响性能)
使用频率 推荐优先使用 仅在必要时使用
适合场景 数据请求、事件监听、非关键 DOM 操作 精确测量 DOM、防止布局抖动

四、实际使用场景示例

场景一:根据 DOM 尺寸进行布局调整

jsx 复制代码
const MeasureBox = () => {
  const boxRef = useRef(null);
  const [size, setSize] = useState({ width: 0, height: 0 });

  useLayoutEffect(() => {
    if (boxRef.current) {
      const { offsetWidth, offsetHeight } = boxRef.current;
      setSize({ width: offsetWidth, height: offsetHeight });
    }
  }, []);

  return (
    <div>
      <div ref={boxRef} style={{ width: '200px', height: '100px', background: 'lightblue' }}>
        Box
      </div>
      <p>Width: {size.width}, Height: {size.height}</p>
    </div>
  );
};

场景二:实现精确动画起始状态

在进入动画前,你需要先获取 DOM 的初始位置,并设置动画起点:

jsx 复制代码
useLayoutEffect(() => {
  const element = ref.current;
  if (element) {
    const initialPosition = element.getBoundingClientRect().top;
    animate(element, { top: initialPosition + 100 });
  }
}, []);

五、注意事项

**推荐优先使用 useEffect,**除非你明确需要在绘制前执行副作用,否则应优先使用 useEffect,因为它不会阻塞浏览器绘制,对性能更友好。

**避免在 useLayoutEffect 中执行耗时操作,**由于它是同步执行的,如果在里面做大量计算或网络请求,会显著延长页面渲染时间,造成"白屏"或"卡顿"。

在 SSR(服务端渲染)中需谨慎使用,useLayoutEffect 在服务端无法执行,因为没有 DOM。如果你在 Next.js 或其他 SSR 框架中使用它,务必确保依赖的数据在客户端可用,或者在 useEffect 中降级处理。

相关推荐
chao_78936 分钟前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼1 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原1 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf1 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵2 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
每天吃饭的羊2 小时前
react中为啥使用剪头函数
前端·javascript·react.js
Nicholas683 小时前
Flutter帧定义与60-120FPS机制
前端
多啦C梦a3 小时前
【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!
前端·javascript·架构
薛定谔的算法3 小时前
《长安的荔枝·事件流版》——一颗荔枝引发的“冒泡惨案”
前端·javascript·编程语言
中微子3 小时前
CSS 的 position 你真的理解了吗?
前端·css