🚀🚀告别页面闪烁: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 信息
性能影响 低(不阻塞) 高(耗时操作会卡顿)
相关推荐
远方的小草16 小时前
Nginx 反向代理
前端
英俊潇洒美少年16 小时前
通用构建优化(编译阶段)+ Vue 专属运行时优化 + React 专属运行时优化
前端·vue.js·react.js
慕斯fuafua16 小时前
CSS——样式
前端·css
知己呀16 小时前
MySQL主从延迟根因诊断与优化指南
经验分享·mysql·面试·八股文·主从延迟
英俊潇洒美少年16 小时前
Vue 和 React 的核心渲染机制 对比
前端·vue.js·react.js
Beginner x_u16 小时前
前端八股整理|VUE|高频小题 01
前端·javascript·vue.js
笨笨狗吞噬者16 小时前
代理的妙用:uni-app 小程序是怎样用 `Proxy` 和 `wrapper` 抹平平台差异的
前端·微信小程序·uni-app
桜吹雪16 小时前
@embedpdf/vue-pdf-viewer内网使用避坑
前端·vue.js
一定要AK16 小时前
Vue 从入门到实战笔记
前端·vue.js·笔记
oi..16 小时前
Web 安全入门:XSS 漏洞原理与防护学习笔记 [ OWASP Top10 漏洞原理学习(仅用于合规测试)]
前端·网络·笔记·安全·网络安全·xss