为什么你的页面总是闪一下?可能是没用对 useLayoutEffect

148 阅读3分钟

在 React 项目里,useEffect 大家用得贼溜,但一提到 useLayoutEffect,很多人要么没用过,要么根本分不清啥时候该用,结果页面总是莫名“闪一下”,用户体验直接拉垮。

别急,今天就用几个最典型的例子,带你一次整明白:

  • 它和 useEffect 到底有啥区别?
  • 它是怎么帮你解决页面闪烁问题的?
  • 什么场景必须用它?

useEffect 和 useLayoutEffect 到底区别在哪?

先看最核心的点:

Hook什么时候执行特点
useEffect浏览器完成渲染后执行异步,不阻塞页面渲染
useLayoutEffectDOM 更新后、浏览器绘制前执行同步,阻塞页面渲染,执行完才渲染到屏幕上

一句话:

  • useEffect 是“渲染完了,咱们再干点事儿”
  • useLayoutEffect 是“DOM 改完了,页面还没画,先干事儿再画出来”

如果副作用跟布局有关(比如测量高度、计算位置),用 useEffect 就会出现一闪而过的布局错误,再被修正过来;useLayoutEffect 就能在页面渲染前把它干好,防止抖动。


场景 1:最简单的高度测量

来看个最简单的示例:

function App() {
  const boxRef = useRef();

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

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

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

输出结果:

useLayoutEffect height 100
useEffect height 100

这里 useLayoutEffect 会比 useEffect 先打印,因为它在 DOM 更新后、浏览器真正渲染前执行。虽然打印的值一样,但它执行得更及时。


场景 2:内容和样式同时修改,防止闪烁

假设有个盒子,第一次渲染时有默认的文字和高度,接着要改成新的文字和高度。如果用 useEffect,你可能会看到先出现旧内容,再闪一下变成新内容 + 新高度,效果很糟糕。

function App() {
  const [content, setContent] = useState('原内容...');
  const ref = useRef();

  useLayoutEffect(() => {
    // 用 useLayoutEffect 阻塞渲染,保证渲染时就已经是新状态
    setContent('新内容...');
    ref.current.style.height = '200px';
  }, []);

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

这里如果换成 useEffect,就会先渲染老状态,用户肉眼可见“抖一下”。useLayoutEffect 则是先把 DOM 改好,浏览器再渲染,视觉上丝滑得多。


场景 3:动态居中弹窗,经典闪烁坑

还有个常见场景:弹窗居中。需要先知道弹窗实际高度,再算 margin,让它上下居中。

function Modal() {
  const ref = useRef();

  useLayoutEffect(() => {
    const height = ref.current.offsetHeight;
    ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`;
  }, []);

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

function App() {
  return <Modal />;
}

如果这里用 useEffect,弹窗一开始会出现在左上角,然后才跳到居中位置,用户体验很割裂。而 useLayoutEffect 可以在 DOM 有了高度后,立刻算好位置,让它一开始就乖乖居中。


什么时候必须用 useLayoutEffect?

总结一下核心原则:

✅ 用 useEffect

  • 异步副作用(请求数据、埋点、订阅)
  • 跟布局无关,不影响用户第一时间看到的页面

✅ 用 useLayoutEffect

  • 需要同步测量 DOM(获取宽高、位置)
  • 需要立刻改动布局(计算位置、设置样式)
  • 有“闪一下”或者“跳一下”的视觉抖动

一句话:
凡是跟“页面渲染前”有关的布局修改,都要用 useLayoutEffect,否则就是肉眼可见的闪烁!


写在最后

以后再遇到“页面莫名其妙闪一下”,先别急着骂浏览器,想想是不是 useLayoutEffect 用对了。

希望这篇文章能帮你彻底分清这两个 Hook 的区别,写出更丝滑的交互体验。


如果对你有帮助,点个赞吧!