React 双剑合璧:useEffect 与 useLayoutEffect 的舞台艺术

0 阅读3分钟

序言

在 React 的舞台上,useEffect 和 useLayoutEffect 如同两位配合默契的后台助手,共同确保演出完美呈现。但你知道他们何时上场,如何协作吗?

舞台上的两位助手

想象你正在导演一场精彩的舞台剧:

  • 🎭 演员 = React 组件
  • 💡 舞台 = 浏览器屏幕
  • 🧙 后台助手1号 = useEffect
  • 🧙 后台助手2号 = useLayoutEffect

助手1号:useEffect - 幕后的完美主义者

useEffect(() => {
  // 演出结束后才工作
  console.log('演出结束,我来打扫后台')
}, [])

助手2号:useLayoutEffect - 幕间的闪电侠

useLayoutEffect(() => {
  // 演出间隙快速调整道具
  console.log('演出间隙,我调整了舞台灯光')
}, [])

关键区别:演出时间表

时刻useEffectuseLayoutEffect观众感受
演员准备阶段❌ 不参与❌ 不参与-
演员表演阶段❌ 不参与❌ 不参与观看表演
表演结束,幕布未落❌ 等待✅ 立即工作看到闪烁
幕布落下后✅ 开始工作❌ 已完成看不到变化

防闪烁魔法:useLayoutEffect 的绝技

想象这个场景:弹窗需要居中显示

问题代码(使用 useEffect):

function Modal() {
  const ref = useRef()
  
  useEffect(() => {
    // 在渲染后调整位置
    const height = ref.current.offsetHeight
    ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`
  }, [])

  return <div ref={ref}>弹窗内容</div>
}

观众体验

  1. 看到弹窗出现在左上角(初始位置)
  2. 瞬间跳到屏幕中央(闪烁)

解决方案(使用 useLayoutEffect):

function Modal() {
  const ref = useRef()
  
  useLayoutEffect(() => {
    // 在渲染前调整位置
    const height = ref.current.offsetHeight
    ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`
  }, [])

  return <div ref={ref}>弹窗内容</div>
}

观众体验

  1. 直接看到居中显示的弹窗(无闪烁)

真实案例:避免内容跳动

动态调整元素高度

function DynamicBox() {
  const ref = useRef()
  const [content, setContent] = useState('短文本')
  
  useLayoutEffect(() => {
    // 同步更新高度
    ref.current.style.height = '200px'
  }, [content])

  return (
    <div 
      ref={ref} 
      style={{ background: 'lightblue' }}
      onClick={() => setContent('很长很长的文本...')}
    >
      {content}
    </div>
  )
}

效果对比

  • 使用 useEffect:点击后先看到短框,然后突然变高(闪烁)
  • 使用 useLayoutEffect:高度变化瞬间完成(无闪烁)

双助手协作的艺术

最佳配合方案

useLayoutEffect(() => {
  // 1. 紧急任务:需要立即执行的DOM操作
  element.style.position = 'absolute'
  element.style.top = '50px'
  
  // 2. 测量布局
  const rect = element.getBoundingClientRect()
  
  return () => {
    // 清理布局副作用
  }
}, [dependencies])

useEffect(() => {
  // 1. 数据获取
  fetchData().then(setData)
  
  // 2. 事件订阅
  window.addEventListener('resize', handleResize)
  
  return () => {
    // 清理数据副作用
    window.removeEventListener('resize', handleResize)
  }
}, [dependencies])

性能权衡:何时用谁?

场景推荐助手原因
DOM 测量/调整useLayoutEffect避免布局闪烁
数据获取useEffect不阻塞渲染
事件订阅useEffect非紧急任务
动画起始useLayoutEffect确保起始状态正确
第三方库集成useEffect通常不需要同步执行

新手指南:黄金法则

  1. 默认首选 useEffect

    • 适用于大多数场景
    • 不会阻塞页面渲染
    • 更符合React的异步特性
  2. 遇到闪烁问题时考虑 useLayoutEffect

    • DOM布局调整
    • 元素位置计算
    • 样式同步更新
  3. 清理工作同样重要

    useLayoutEffect(() => {
      // 设置DOM属性
      element.style.color = 'red'
      
      return () => {
        // 恢复原始状态
        element.style.color = ''
      }
    }, [])
    

幕后原理揭秘

React 更新流程:

deepseek_mermaid_20250711_057055.png

  1. useLayoutEffect 阶段

    • DOM已更新,但浏览器还未绘制
    • 同步执行,会阻塞浏览器绘制
  2. useEffect 阶段

    • 浏览器已完成绘制
    • 异步执行,不阻塞页面

总结:完美舞台的秘诀

在 React 的舞台上:

  • useEffect 是幕后的清洁工和联络员

    • 演出结束后工作
    • 处理数据、订阅等"后台事务"
  • useLayoutEffect 是幕间的道具师

    • 演出间隙快速调整
    • 解决视觉闪烁问题
    • 处理DOM布局相关操作

记住这个黄金法则:

当你在屏幕上看到内容后才需要调整 - 用 useEffect
当你需要在用户看到前完成调整 - 用 useLayoutEffect

掌握这两位助手的协作艺术,你就能打造出流畅无闪烁的 React 应用!