useLayoutEffect:React 中的"闪电侠"与"时间冻结者"

在 React 的魔法世界里,有两个极其相似的咒语:useEffectuseLayoutEffect。它们就像魔法学院里的一对双胞胎兄弟,外表相似但性格迥异。今天,我们就来揭开这对"特效双雄"的神秘面纱!

先看这对兄弟的简历

useEffect(异步侠)

  • 出场时机:组件渲染完成后(浏览器绘制之后)
  • 特长:优雅地处理副作用(数据获取、订阅等)
  • 口头禅:"让子弹飞一会儿"

useLayoutEffect(闪电侠)

  • 出场时机:DOM更新后但浏览器绘制前
  • 必杀技:冻结时间修改DOM
  • 座右铭:"我比光还快!"

这就像你在厨房做三明治:

  • useEffect 是先把三明治端上桌,再调整摆盘
  • useLayoutEffect 是在端上桌前就调整好摆盘

感觉useLayoutEffect很像DOMcontentloaded,是DOM更新后执行

实战:

  1. 高度测量之争
javascript 复制代码
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>
}

运行这段代码,控制台会显示:

arduino 复制代码
useLayoutEffect height 100
useEffect height 100

虽然结果相同,但顺序暴露了真相!useLayoutEffect 就像百米赛跑的博尔特,总能在浏览器绘制前抢先执行。而 useEffect 则是悠闲的观光客,等页面渲染完才慢悠悠登场。

  1. 闪烁大作战:文字变形记
csharp 复制代码
function App() {
  const [content, setContent] = useState("曾经有一份真诚的爱情...")
  const ref = useRef()
  
  useLayoutEffect(() => {
    setContent("React并不会立即更新状态...")
    ref.current.style.height = '200px'
  }, [])
  
  return (
    <div ref={ref} style={{ height: '50px', background: 'lightblue' }}>
      {content}
    </div>
  )
}

这里发生了什么魔法?

  1. 初始状态:50px高度 + 星爷经典台词

  2. useLayoutEffect 闪电出击:

    • 更换为技术说明文本
    • 调整高度为200px

关键点:所有操作在浏览器绘制前完成!用户只看到最终效果,完全看不到中间状态。

如果换成 useEffect,用户会先看到矮胖的星爷台词,然后突然变成高瘦的技术文本------这就是恼人的"闪烁"效果!

可以看到下述效果,我们是很明显的可以看到闪烁效果,其实就是因为useEffect发生在渲染后,从无到有,给人一种闪烁感,而useLayoutEffect是在DOM构建之后,阻塞页面渲染,避免了这种情况

比喻:useLayoutEffect 像魔术师的暗箱操作,在幕布拉起前完成所有准备;而 useEffect 则是在观众注视下换道具,难免露馅。

  1. 弹窗居中:几何学魔法
csharp 复制代码
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={{ 
      position: 'absolute', 
      height: '200px', 
      width: '200px', 
      background: 'red' 
    }}>
      我是弹窗
    </div>
  )
}

这个弹窗为何如此优雅?秘密在于:

  1. 先让弹窗以默认位置渲染(但此时位置不对)
  2. useLayoutEffect 瞬间测量弹窗高度
  3. 计算并设置垂直居中

整个过程快到用户无法察觉!就像超人换装------你永远只看到他穿着整齐的样子。

我们为什么要这么做呢?大家都知道重绘重排会影响性能,而const height = ref.current.offsetHeightref.current.style.marginTop = ${(window.innerHeight - height) / 2}px 是会引起重绘重排的,那我们为什么不等它们在页面渲染前就完成工作,就不需要重绘了,这时候我们的 useLayoutEffect 就起了大作用,DOM结构构建完成,就进行工作,解决了烦恼

  1. 原理深潜:React 渲染流水线

理解执行时机是掌握这对兄弟的关键:

markdown 复制代码
1. 组件渲染(生成虚拟DOM)
2. DOM更新(应用变更到真实DOM)
3. 🚨 useLayoutEffect 执行 🚨
4. 浏览器绘制(像素上屏)
5. useEffect 执行

当你在 useLayoutEffect 中修改 DOM 时:

  • React 会中断浏览器绘制流程
  • 重新回到步骤2(DOM更新)
  • 形成神奇的"时间循环"

类比:这就像导演在电影上映前最后一刻修改镜头,观众看到的始终是最终版本。

  1. 性能警示:闪电侠的代价

能力越大,责任越大。useLayoutEffect 的同步特性是把双刃剑:

scss 复制代码
// 危险操作!可能导致界面冻结
useLayoutEffect(() => {
  // 复杂计算或大数据处理...
}, [])

黄金法则

  1. 当需要操作 DOM 避免视觉闪烁时 → 选 useLayoutEffect
  2. 数据获取、日志记录等异步操作 → 用 useEffect
  3. 不确定时 → 先用 useEffect,遇到闪烁再考虑切换

因为useLayoutEffect会阻塞页面渲染,如果回调函数是执行复杂的逻辑,会很大程度阻塞页面渲染,极大地影响用户的体验,最终换来的肯定是用户的 X

趣味实验:肉眼可见的差异

让我们制造一个可控闪烁(请在安全环境下尝试):

javascript 复制代码
function FlashDemo() {
  const ref = useRef()
  const [style, setStyle] = useState({ background: 'blue' })
  
  useEffect(() => { // 换成 useLayoutEffect 观察变化
    setTimeout(() => {
      setStyle({ background: 'red', height: '200px' })
    }, 1000)
  }, [])
  
  return <div ref={ref} style={style}>变色龙区块</div>
}

useEffect 时你会看到:

  1. 蓝色方块显示1秒
  2. 突然变成红色并长高

换成 useLayoutEffect 后:

  1. 页面保持空白1秒(阻塞渲染!)
  2. 直接显示最终的红色高方块

这个实验完美展示了二者的本质区别!

哲学思考:时间管理大师

React 团队设计这两个钩子,本质上是在解决时机选择这个古老哲学问题:

  • useEffect 代表"后见之明"(渲染后再调整)
  • useLayoutEffect 代表"未雨绸缪"(渲染前搞定一切)

就像建筑工地的两种监工:

  • useEffect:"楼盖好了我们再调整门窗位置"
  • useLayoutEffect:"在砌墙时就把门窗安到正确位置"

终极对决:何时召唤谁?

场景 推荐选手 原因
DOM 测量/布局调整 useLayoutEffect 避免布局跳动和闪烁
数据获取 useEffect 不阻塞渲染,更好的性能
动画初始化 useLayoutEffect 确保动画开始时元素已在正确位置
事件监听/订阅 useEffect 清理函数更安全
第三方 DOM 库集成 useLayoutEffect 确保库初始化时 DOM 已更新

结语:掌握时间的大师

经过这次探险,我们终于理解了:

  • useEffect 是优雅的异步诗人
  • useLayoutEffect 是精准的同步外科医生

记住这个魔法口诀:
"闪烁用 Layout,异步用 Effect"

下次当你看到页面元素跳舞时,别急着怪显示器------可能是你选错了特效钩子!现在就去给你的组件们安排正确的时机管理者吧,它们会回报你丝般顺滑的用户体验!

相关推荐
chao_78913 分钟前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼26 分钟前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原1 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf1 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵1 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
阳火锅2 小时前
Vue 开发者的外挂工具:配置一个 JSON,自动造出一整套页面!
javascript·vue.js·面试
每天吃饭的羊2 小时前
react中为啥使用剪头函数
前端·javascript·react.js
Nicholas682 小时前
Flutter帧定义与60-120FPS机制
前端
多啦C梦a2 小时前
【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!
前端·javascript·架构
薛定谔的算法2 小时前
《长安的荔枝·事件流版》——一颗荔枝引发的“冒泡惨案”
前端·javascript·编程语言