🚀🚀告别页面闪烁: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 信息
性能影响 低(不阻塞) 高(耗时操作会卡顿)
相关推荐
若惜5 小时前
selenium自动化测试web自动化测试 框架封装Pom
前端·python·selenium
Amumu121385 小时前
Js:内置对象
开发语言·前端·javascript
广州华水科技5 小时前
2026年单北斗GNSS变形监测系统推荐,助力精准监控与智慧城市建设
前端
鸡吃丸子5 小时前
如何编写一个高质量的AI Skill
前端·ai
独自破碎E6 小时前
【面试真题拆解】Spring事务机制
java·spring·面试
程序员爱钓鱼6 小时前
Go PDF处理利器: github.com/pdfcpu/pdfcpu 深度指南
后端·面试·go
我命由我123456 小时前
Element Plus 2.2.27 的单选框 Radio 组件,选中一个选项后,全部选项都变为选中状态
开发语言·前端·javascript·html·ecmascript·html5·js
Luna-player6 小时前
第3章 Spring Boot的Web应用支持,个人学习笔记
前端·spring boot·学习
bugcome_com6 小时前
【ASP.NET Web Pages】页面布局核心实战:从复用性到安全性,打造一致化网站界面
前端·后端·asp.net
Sylus_sui6 小时前
Class 模型 + 跨组件状态(@Observed)+ 网络请求封装 + 本地存储全部是鸿蒙 Next/Stage 模型标准写法
前端