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

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

相关推荐
花归去2 分钟前
echarts 柱状图曲线图
开发语言·前端·javascript
喝拿铁写前端2 分钟前
当 AI 会写代码之后,我们应该怎么“管”它?
前端·人工智能
老前端的功夫6 分钟前
TypeScript 类型魔术:模板字面量类型的深层解密与工程实践
前端·javascript·ubuntu·架构·typescript·前端框架
Nan_Shu_61429 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#37 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界1 小时前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路1 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug1 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121381 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化