useLayoutEffect:你以为它和useEffect是"亲兄弟"?其实差别大了!

大家好,我是小杨。今天想和大家聊聊React中一个容易被误解的Hook------useLayoutEffect。很多人觉得它和useEffect长得像,用起来也差不多,但实际上这对"兄弟"的性格差异可不小!

记得我刚接触useLayoutEffect时,也曾天真地以为:"这不就是个同步版的useEffect吗?"结果在真实项目中踩了几个坑后,才发现事情没那么简单。


一、先来认识一下:useLayoutEffect是什么?

简单来说,useLayoutEffect是useEffect的"急性子兄弟"。它的函数签名和useEffect一模一样,但执行时机却大不相同。

jsx 复制代码
import { useLayoutEffect, useState } from 'react';

function MyComponent() {
  const [width, setWidth] = useState(0);
  
  useLayoutEffect(() => {
    const measuredWidth = document.getElementById('myElement').offsetWidth;
    setWidth(measuredWidth);
  }, []);
  
  return <div id="myElement">我的宽度是:{width}px</div>;
}

看到这里你可能会问:"这用useEffect不也能实现吗?"别急,让我们继续往下看。


二、关键区别:什么时候执行?

这是useLayoutEffect和useEffect最核心的区别:

  • useEffect:异步执行,在浏览器绘制完成后运行
  • useLayoutEffect:同步执行,在浏览器绘制前运行

换句话说,useLayoutEffect会阻塞浏览器的绘制,直到它的回调函数执行完毕。

jsx 复制代码
// 用例:在页面渲染前测量DOM元素
function Tooltip({ content }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const tooltipRef = useRef(null);
  
  useLayoutEffect(() => {
    const rect = tooltipRef.current.getBoundingClientRect();
    // 在用户看到之前就计算好位置
    setPosition({
      x: window.innerWidth - rect.width - 10,
      y: 10
    });
  }, [content]);
  
  return (
    <div ref={tooltipRef} style={{ position: 'fixed', left: position.x, top: position.y }}>
      {content}
    </div>
  );
}

三、什么时候该用useLayoutEffect?

经过这些年的实践,我总结出几个useLayoutEffect的典型使用场景:

1. 避免视觉闪烁

当你需要基于DOM测量结果进行渲染时,useLayoutEffect可以避免用户看到中间状态。

jsx 复制代码
function ResizablePanel({ children }) {
  const [size, setSize] = useState(0);
  const containerRef = useRef(null);
  
  useLayoutEffect(() => {
    const containerWidth = containerRef.current.offsetWidth;
    // 在渲染前计算合适的大小
    setSize(containerWidth * 0.8);
  }, []);
  
  return (
    <div ref={containerRef}>
      <div style={{ width: `${size}px` }}>
        {children}
      </div>
    </div>
  );
}

2. 同步布局操作

需要同步读取布局属性并立即基于这些属性进行更新时。

jsx 复制代码
function StickyHeader() {
  const [isSticky, setIsSticky] = useState(false);
  const headerRef = useRef(null);
  
  useLayoutEffect(() => {
    const checkPosition = () => {
      const rect = headerRef.current.getBoundingClientRect();
      setIsSticky(rect.top <= 0);
    };
    
    checkPosition();
    window.addEventListener('scroll', checkPosition);
    
    return () => window.removeEventListener('scroll', checkPosition);
  }, []);
  
  return <header ref={headerRef} className={isSticky ? 'sticky' : ''}>头部</header>;
}

四、我踩过的坑:性能问题

useLayoutEffect虽然强大,但滥用会导致性能问题。我曾经在一个项目中过度使用useLayoutEffect,结果导致了明显的性能下降。

jsx 复制代码
// 错误示范:在useLayoutEffect中做重操作
useLayoutEffect(() => {
  // 这个繁重的计算会阻塞页面渲染!
  const heavyData = processLargeData(props.data);
  setProcessedData(heavyData);
}, [props.data]);

经验法则:除非确实需要在绘制前同步执行操作,否则优先使用useEffect。


五、实用技巧:如何选择?

根据我的经验,选择useEffect还是useLayoutEffect可以遵循这个流程:

  1. 先用useEffect:大多数情况下它都是正确的选择
  2. 如果出现视觉闪烁:考虑切换到useLayoutEffect
  3. 测量DOM布局:需要同步获取布局信息时用useLayoutEffect
  4. 性能敏感:在useLayoutEffect中避免繁重计算

六、总结:兄弟虽像,各有所长

  • useEffect:适用于大多数副作用,不会阻塞渲染
  • useLayoutEffect:适用于需要同步布局测量的场景

记住一个简单的比喻:useEffect就像寄普通快递,不着急;useLayoutEffect就像闪送,必须马上送到!

最后给大家的建议: "不要因为useLayoutEffect听起来很酷就滥用它,大多数时候useEffect才是你的好朋友。"

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
Larcher7 小时前
React Router 不只是页面跳转:从 SPA 路由到权限守卫的完整实践
javascript·后端
Larcher7 小时前
大模型为什么每次回答都不一样?一文搞懂 Temperature、Top K 与 Top P
javascript·后端
用户9385156350710 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
CodeSheep10 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员
鱼樱前端10 小时前
用 AI 做内容变收入
前端·人工智能·ai编程
eric-sjq10 小时前
10 分钟实战:用 0.6B 本地模型生成中文网页(WanlyFrontend + 编译器全流程)
javascript·机器学习·自然语言处理·html
浮生望10 小时前
React + TypeScript 组件设计进阶:从类型约束到无状态组件的三次进化
前端
To_OC11 小时前
LC 438 找到所有字母异位词:暴力超时后,我靠滑动窗口一招搞定
javascript·算法·leetcode
90后的晨仔11 小时前
Mac 开发 uni-app 终极方案:让安卓模拟器彻底脱离 Android Studio 独立运行
前端·vue.js
90后的晨仔11 小时前
别再搞混了!uni-app 中 node_modules 和 uni_modules 到底是什么关系?
前端