🚀🚀告别页面闪烁:useLayoutEffect 从原理到实战的正确用法

为什么你的React组件总在闪烁?为什么元素位置计算总是不准确?答案就藏在useLayoutEffect中!在此前已经学习过了useEffect,二者又有什么不同呢?

useEffect 与 useLayoutEffect 的核心区别:执行时机

两者都是处理副作用的钩子,核心差异在于执行时机不同,这直接导致了它们在实际使用中的效果差异。

useEffect 的执行时机

useEffect的副作用会在组件渲染完成后(DOM 更新并已绘制到屏幕) 执行,属于 "异步" 执行,不会阻塞浏览器的渲染流程。

JSX 复制代码
useEffect(() => {
  // 此时DOM已更新并显示在屏幕上
  console.log('useEffect:渲染完成后执行');
}, []);

useLayoutEffect 的执行时机

useLayoutEffect的副作用会在DOM 更新后、屏幕渲染前执行,属于 "同步" 执行,会阻塞渲染流程。

JSX 复制代码
useLayoutEffect(() => {
  // 此时DOM已更新,但尚未显示在屏幕上
  console.log('useLayoutEffect:DOM更新后,渲染前执行');
}, []);

直观对比:执行顺序

jsx 复制代码
function App() {
  const ref = useRef();

  useEffect(() => {
    console.log('useEffect:', ref.current.offsetHeight);
  }, []);

  useLayoutEffect(() => {
    console.log('useLayoutEffect:', ref.current.offsetHeight);
  }, []);

  return <div ref={ref} style={{ height: 100 }}></div>;
}

关键差异useLayoutEffect的代码会在用户看到新 UI 之前执行,而useEffect的代码在用户看到新 UI 之后执行。

useLayoutEffect 能解决什么问题?核心价值

useLayoutEffect的 "同步阻塞渲染" 特性,使其特别适合解决因 DOM 操作延迟导致的 UI 闪烁或抖动问题

案例:修改文本内容和容器高度

jsx 复制代码
function TextBox() {
  const [content, setContent] = useState('初始长文本...');
  const ref = useRef();

  useEffect(() => {
    // 1. 修改文本内容
    setContent('更新后的文本内容...');
    // 2. 同步修改容器高度
    ref.current.style.height = '200px';
  }, []);

  return (
    <div 
      ref={ref} 
      style={{ height: '100px', background: 'lightblue' }}
    >
      {content}
    </div>
  );
}

在刷新的时候,我们可以明显看到页面的闪烁,那么

为什么 useEffect 会闪烁?

  1. 初始渲染:显示 "初始长文本",高度 100px(已绘制到屏幕);
  2. useEffect执行:修改文本和高度为 200px;
  3. 再次渲染:更新为最终状态。

用户会看到 "初始文本 + 100px 高度" 瞬间切换到 "新文本 + 200px 高度",出现闪烁。

而当把useEffect换成useLayoutEffect时 ,流程会变成:

  1. 初始渲染计算 DOM(未绘制);
  2. useLayoutEffect执行:修改文本和高度(DOM 更新);
  3. 一次性绘制最终状态到屏幕。

用户直接看到最终结果,无中间状态,避免闪烁。

同步获取 DOM 信息:确保拿到最新状态

在需要获取 DOM 元素的样式、尺寸(如offsetHeightscrollTop)时,useLayoutEffect能保证拿到的是 "更新后、未渲染" 的最新 DOM 状态,而useEffect可能因异步执行导致获取到旧状态(极端场景下)。 案例:弹窗居中定位

jsx 复制代码
function Modal() {
  const ref = useRef();

  useLayoutEffect(() => {
    // 获取弹窗高度和窗口高度,计算居中位置
    const modalHeight = ref.current.offsetHeight;
    const windowHeight = window.innerHeight;
    // 同步设置marginTop,确保渲染时已居中
    ref.current.style.marginTop = `${(windowHeight - modalHeight) / 2}px`;
  }, []);

  return (
    <div 
      ref={ref} 
      style={{ 
        position: 'absolute', 
        width: '200px', 
        height: '150px', 
        background: 'red' 
      }}
    >
      居中弹窗
    </div>
  );
}

为什么用 useLayoutEffect?

弹窗需要基于自身高度计算居中位置,useLayoutEffect在 DOM 更新后立即执行,能同步拿到准确的offsetHeight,并在渲染前设置好位置,确保弹窗一出现就是居中的。

若用useEffect,可能在获取高度时弹窗尚未完成 DOM 更新,导致计算错误,出现定位偏差(后续再修正,产生抖动)。

useLayoutEffect 的使用注意事项

(1)避免耗时操作:会阻塞渲染

useLayoutEffect在渲染前执行,且是同步阻塞的。如果在其中写耗时逻辑(如复杂循环、大量计算),会延迟页面渲染,导致用户看到 "白屏" 或 "卡顿"。

jsx 复制代码
useLayoutEffect(() => {
  // 耗时操作:阻塞渲染
  for (let i = 0; i < 100000000; i++) {} 
}, []);

(2)依赖项与执行时机

useEffect一样,useLayoutEffect的第二个参数是依赖项数组:

  • 空数组[]:仅在组件挂载后执行一次;
  • 依赖项变化:在 DOM 更新后、渲染前执行;
  • 无依赖项:每次渲染后(DOM 更新后)都执行。

(3)与 useEffect 的选择原则

  • 优先用useEffect:大多数场景下,异步执行不阻塞渲染,性能更好;

  • useLayoutEffect的场景:

    1. 需要修改 DOM 以避免 UI 闪烁(如上述文本 + 高度修改案例);
    2. 需要同步获取 DOM 信息并立即应用(如弹窗居中、滚动位置调整)。

核心区别总结

特性 useEffect useLayoutEffect
执行时机 渲染完成后(已绘制到屏幕) DOM 更新后、渲染前(未绘制)
是否阻塞渲染 不阻塞(异步) 阻塞(同步)
适用场景 大多数副作用(数据请求、事件监听) 处理 DOM 避免闪烁、同步获取 DOM 信息
性能影响 低(不阻塞) 高(耗时操作会卡顿)
相关推荐
前端小趴菜0525 分钟前
React - createPortal
前端·vue.js·react.js
晓13131 小时前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
倔强青铜三1 小时前
苦练Python第18天:Python异常处理锦囊
人工智能·python·面试
菜包eo1 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
倔强青铜三1 小时前
苦练Python第17天:你必须掌握的Python内置函数
人工智能·python·面试
烛阴2 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
军军君012 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具四:后端项目基础框架搭建下
spring boot·spring·面试·elementui·typescript·uni-app·mybatis
chao_7893 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼3 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原3 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序