useEffect 和 useLayoutEffect 的区别?

执行时机

Hook 执行时机 阻塞渲染 适合场景
useEffect DOM 更新后异步执行 ❌ 数据请求、订阅、日志
useLayoutEffect DOM 更新后同步执行(绘制前) ✅ 读取/修改 DOM 布局

执行流程

状态变化 → React 渲染 → DOM 更新
  → useLayoutEffect 同步执行 ← 此时浏览器还未绘制
  → 浏览器绘制到屏幕
  → useEffect 异步执行 ← 此时用户已看到画面

useLayoutEffect 使用场景

需要读取 DOM 布局并同步修改时使用,避免闪烁:

function Tooltip({ children }) {
  const ref = useRef()
  const [position, setPosition] = useState({ x: 0, y: 0 })

  useLayoutEffect(() => {
    // 读取 DOM 位置
    const rect = ref.current.getBoundingClientRect()
    // 同步更新位置,避免闪烁
    setPosition({
      x: rect.x + rect.width,
      y: rect.y
    })
  }, [children])

  return (
    <div ref={ref} style={{ transform: `translate(${position.x}px, ${position.y}px)` }}>
      {children}
    </div>
  )
}

如果用 useEffect 会怎样

// ❌ 用户会先看到 tooltip 在错误位置,然后跳到正确位置(闪烁)
useEffect(() => {
  const rect = ref.current.getBoundingClientRect()
  setPosition({ x: rect.x + rect.width, y: rect.y })
}, [children])

useEffect 常见用法

// 数据请求(异步,不阻塞渲染)
useEffect(() => {
  fetch('/api/data')
    .then(res => res.json())
    .then(data => setData(data))
}, [])

// 事件监听
useEffect(() => {
  window.addEventListener('resize', handleResize)
  return () => window.removeEventListener('resize', handleResize)
}, [])

// 依赖变化重新执行
useEffect(() => {
  if (userId) fetchUser(userId)
}, [userId])

SSR 注意

useLayoutEffect 在 SSR 中会报警告(服务端没有 DOM),可用 useIsomorphicLayoutEffect 兼容:

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

同分类其他题目