在最近一次面试中被问到,我印象中好像从来没用过useLayoutEffect,就没答上来。但是看名字应该是跟布局相关的,而且跟useEffect会有类似的作用。
在 React
中,useEffect
和 useLayoutEffect
都是用于处理副作用的 Hooks
,但它们的执行时机和对渲染流程的影响有显著区别。以下是两者的核心差异及使用场景:
Hook | 执行实际 |
---|---|
useEffect | 异步执行:在浏览器完成布局和绘制(Paint)后触发。 |
useLayoutEffect | 同步执行:在 React 完成 DOM 更新后、浏览器绘制前触发,会阻塞浏览器渲染。 |
React 组件更新 → 计算 DOM 变更 → 更新 DOM → [useLayoutEffect 执行] → 浏览器绘制 → [useEffect 执行]
Hook | 用户感知 |
---|---|
useEffect | 副作用可能稍后执行,用户可能先看到未更新的界面,再看到更新后的内容(短暂闪烁)。 |
useLayoutEffect | 副作用在绘制前完成,用户直接看到最终结果,避免视觉不一致。 |
js// 使用 useEffect(可能闪烁)
useEffect(() => {
const element = document.getElementById("my-element");
element.style.color = "red"; // 浏览器可能先绘制默认颜色,再变红
}, []);
// 使用 useLayoutEffect(无闪烁)
useLayoutEffect(() => {
const element = document.getElementById("my-element");
element.style.color = "red"; // 在绘制前完成修改
}, []);
Hook | 典型场景 |
---|---|
useEffect | 数据获取(API 请求)/ 订阅事件(如 WebSocket) / 不需要同步 DOM 的操作 |
useLayoutEffect | 测量 DOM 元素尺寸/位置(如动态调整布局) / 同步修改 DOM 样式以避免闪烁 |
jsuseLayoutEffect(() => {
const element = document.getElementById("my-element");
const width = element.offsetWidth;
setElementWidth(width); // 在绘制前获取并更新尺寸
}, []);
异步执行,不会阻塞渲染,适合大多数场景,尤其是耗时操作(如网络请求)。
同步执行,若副作用逻辑复杂可能导致渲染延迟,需谨慎使用。
无此问题,适合 SSR
环境。
在服务端渲染时,React
会发出警告(因为其逻辑无法在服务端执行),需替换为 useEffect 或在客户端渲染后执行。
场景:点击按钮后动态调整元素高度
js// 使用 useEffect(可能闪烁)
function Component() {
const [height, setHeight] = useState(0);
const ref = useRef();
useEffect(() => {
ref.current.style.height = "100px"; // 可能先渲染默认高度,再调整
}, []);
return <div ref={ref}>Height: {height}px</div>;
}
// 使用 useLayoutEffect(无闪烁)
function Component() {
const [height, setHeight] = useState(0);
const ref = useRef();
useLayoutEffect(() => {
ref.current.style.height = "100px"; // 在绘制前完成调整
}, []);
return <div ref={ref}>Height: {height}px</div>;
}
维度 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 异步(绘制后) | 同步(绘制前) |
用户体验 | 可能闪烁 | 无闪烁 |
性能影响 | 低 | 可能阻塞渲染 |
适用场景 | 数据获取、订阅事件等异步操作 | 同步 DOM 操作(测量、调整样式) |
SSR 兼容性 | 兼容 | 需特殊处理 |
本文作者:CreatorRay
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!