中山市中国灵车网

React使用useLayoutEffect解决操作DOM页面闪烁问题的方法

2026-04-03 10:11:02 浏览次数:0
详细信息

useLayoutEffect 解决DOM操作页面闪烁问题的方法。

为什么会出现页面闪烁?

在React中,使用 useEffect 执行DOM操作时,它会在浏览器绘制之后执行,导致:

浏览器先渲染初始状态 然后执行 useEffect 中的DOM操作 浏览器重新绘制

这个"渲染 → 修改 → 再渲染"的过程可能导致用户看到页面闪烁。

useLayoutEffect 的解决方案

useLayoutEffect 在浏览器绘制之前同步执行,所以DOM操作会和初始渲染一起完成。

import React, { useLayoutEffect, useRef, useState } from 'react';

function Example() {
  const [width, setWidth] = useState(0);
  const elementRef = useRef(null);

  // 使用 useLayoutEffect 在绘制前测量并更新
  useLayoutEffect(() => {
    if (elementRef.current) {
      const { width } = elementRef.current.getBoundingClientRect();
      setWidth(width);
    }
  }, []);

  return (
    <div ref={elementRef}>
      元素宽度: {width}px
    </div>
  );
}

常见使用场景

1. 自动聚焦输入框(无闪烁)

function AutoFocusInput() {
  const inputRef = useRef(null);

  useLayoutEffect(() => {
    // 在页面绘制前聚焦,用户不会看到未聚焦状态
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} placeholder="自动聚焦" />;
}

2. 测量DOM元素并立即调整

function DynamicHeightBox() {
  const [height, setHeight] = useState(0);
  const contentRef = useRef(null);

  useLayoutEffect(() => {
    if (contentRef.current) {
      const contentHeight = contentRef.current.scrollHeight;
      // 立即设置高度,避免内容闪动
      setHeight(contentHeight);
    }
  }, [content]); // content变化时重新测量

  return (
    <div style={{ height: height || 'auto' }}>
      <div ref={contentRef}>
        {/* 动态内容 */}
      </div>
    </div>
  );
}

3. 同步滚动位置

function ScrollSync({ list }) {
  const listRef = useRef(null);
  const [scrollTop, setScrollTop] = useState(0);

  useLayoutEffect(() => {
    const handleScroll = () => {
      if (listRef.current) {
        setScrollTop(listRef.current.scrollTop);
      }
    };

    const element = listRef.current;
    element?.addEventListener('scroll', handleScroll);

    return () => element?.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <div ref={listRef} style={{ overflow: 'auto', height: '400px' }}>
      {/* 列表内容 */}
    </div>
  );
}

实际案例对比

❌ 使用 useEffect(可能闪烁)

function FlashingComponent() {
  const [visible, setVisible] = useState(false);
  const divRef = useRef(null);

  useEffect(() => {
    // 这里会在绘制后执行,用户可能先看到div,然后立即隐藏
    if (divRef.current) {
      divRef.current.style.opacity = '0.5';
    }
    setVisible(true);
  }, []);

  return <div ref={divRef}>{visible ? '已加载' : '加载中...'}</div>;
}

✅ 使用 useLayoutEffect(无闪烁)

function StableComponent() {
  const [visible, setVisible] = useState(false);
  const divRef = useRef(null);

  useLayoutEffect(() => {
    // 在绘制前设置,用户直接看到最终状态
    if (divRef.current) {
      divRef.current.style.opacity = '0.5';
    }
    setVisible(true);
  }, []);

  return <div ref={divRef}>{visible ? '已加载' : '加载中...'}</div>;
}

最佳实践建议

优先使用 useEffect

// 除非必要,否则优先使用 useEffect
// useLayoutEffect 会阻塞浏览器绘制

何时使用 useLayoutEffect

服务端渲染(SSR)注意事项

// 在SSR中,useLayoutEffect 会在服务端执行,可能产生警告
// 解决方案:条件使用
import { useEffect, useLayoutEffect } from 'react';

const useIsomorphicLayoutEffect = 
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

function MyComponent() {
  useIsomorphicLayoutEffect(() => {
    // DOM操作
  }, []);
}

性能考虑

// 避免在 useLayoutEffect 中执行昂贵操作
useLayoutEffect(() => {
  // ✅ 好的:DOM测量、同步样式更新
  element.style.transform = `translateX(${x}px)`;

  // ❌ 避免:大量计算、数据获取
  // const data = expensiveCalculation();
  // fetchData().then(...);
}, [x]);

总结

方面 useEffect useLayoutEffect
执行时机 绘制之后,异步 绘制之前,同步
是否阻塞渲染
适用场景 数据获取、订阅、事件监听 DOM测量、同步样式更新
页面闪烁 可能发生 避免闪烁

使用原则:只有在需要避免视觉不一致或闪烁时,才使用 useLayoutEffect。对于大多数副作用,useEffect 是更好的选择,因为它不会阻塞浏览器绘制。

相关推荐