🚀🚀告别页面闪烁: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 元素的样式、尺寸(如offsetHeight、scrollTop)时,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 分钟前
GoWind Admin 拆解:打印模板设计器——从画布到服务端 PDF 的完整打印链路
前端·后端·go
总要学点什么15 分钟前
第96篇:三维数据加载-城市建筑白模样式+着色器——让色带按体量上色,最白的那档却涂不上去
前端·gis·webgl·cesium·着色器·空间分析·白模
Hilaku1 小时前
你们公司有 35 岁以上的前端吗?
前端·javascript·程序员
徐小夕1 小时前
开源Vector-Graph-RAG:向量+图谱双引擎,打破纯向量RAG的效果天花板
前端·vue.js·github
SameX1 小时前
HarmonyOS ArkTS 把组件截图保存成 PNG 再调系统分享面板的做法
前端
Swot1 小时前
SvelteKit SSG 发布后,为什么老访客非要强刷才能看到新内容?
前端
鸿蒙开发2 小时前
小程序存档写到一半怎么办?双槽保存与损坏恢复实战
前端·javascript·react.js
卸任3 小时前
AI 编程工具越用越多,模型配置越来越乱:用 Magpie 把它们管起来
前端·ai编程
前端一课3 小时前
凝神诀
前端
Rain的Java大神之路3 小时前
你背的Spring答案,全是5.x时代的:7.0.8源码一翻,80%的博客都过时了
java·后端·面试