大家好,我是小杨。今天想和大家聊聊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可以遵循这个流程:
- 先用useEffect:大多数情况下它都是正确的选择
- 如果出现视觉闪烁:考虑切换到useLayoutEffect
- 测量DOM布局:需要同步获取布局信息时用useLayoutEffect
- 性能敏感:在useLayoutEffect中避免繁重计算
六、总结:兄弟虽像,各有所长
- useEffect:适用于大多数副作用,不会阻塞渲染
- useLayoutEffect:适用于需要同步布局测量的场景
记住一个简单的比喻:useEffect就像寄普通快递,不着急;useLayoutEffect就像闪送,必须马上送到!
最后给大家的建议: "不要因为useLayoutEffect听起来很酷就滥用它,大多数时候useEffect才是你的好朋友。"
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!