🚀🚀告别页面闪烁: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 信息
性能影响 低(不阻塞) 高(耗时操作会卡顿)
相关推荐
0和1的舞者2 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
web小白成长日记2 小时前
在Vue样式中使用JavaScript 变量(CSS 变量注入)
前端·javascript·css·vue.js
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之which命令(实操篇)
linux·运维·服务器·前端·chrome·笔记
C_心欲无痕2 小时前
react - useImperativeHandle让子组件“暴露方法”给父组件调用
前端·javascript·react.js
BullSmall4 小时前
支持离线配置修改及删除操作的实现方案
前端
全栈前端老曹5 小时前
【前端路由】Vue Router 嵌套路由 - 配置父子级路由、命名视图、动态路径匹配
前端·javascript·vue.js·node.js·ecmascript·vue-router·前端路由
EndingCoder5 小时前
安装和设置 TypeScript 开发环境
前端·javascript·typescript
萧曵 丶5 小时前
ArrayList 和 HashMap 自动扩容机制详解
java·开发语言·面试
张雨zy5 小时前
Vue 项目管理数据时,Cookie、Pinia 和 LocalStorage 三种常见的工具的选择
前端·javascript·vue.js
五月君_5 小时前
Nuxt UI v4.3 发布:原生 AI 富文本编辑器来了,Vue 生态又添一员猛将!
前端·javascript·vue.js·人工智能·ui