基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

useEffect 和 useLayoutEffect 有什么区别?

知识点图片

好的,useEffectuseLayoutEffect 是 React Hooks 中两个用于处理副作用的 API,它们的主要区别在于执行时机。理解这个区别对于避免页面闪烁、优化性能至关重要。

下面通过一个表格和详细解释来说明它们的区别。

核心区别对比表

特性 useEffect useLayoutEffect
执行时机 浏览器完成绘制(paint)之后,异步执行。 DOM 更新完成后,浏览器绘制之前,同步执行。
阻塞渲染 。不会阻塞浏览器绘制。 。会阻塞浏览器绘制,直到其回调执行完毕。
使用场景 绝大多数副作用操作:数据获取、订阅、手动修改 DOM(不影响布局)、日志记录等。 需要读取 DOM 布局并同步触发重新渲染的场景,例如测量元素尺寸或位置后立即调整 UI。
类比于 Class 组件 componentDidMount, componentDidUpdate, componentWillUnmount (但执行时机更晚) componentDidMount, componentDidUpdate (但与渲染阶段同步)

详细解释与示例

1. useEffect - “事后处理”

  • 工作原理:当函数组件进行渲染(Render)后,React 会将 useEffect 的回调函数推入一个队列中,等待当前渲染周期结束、浏览器完成屏幕绘制后,再在下一个事件循环中按顺序执行。
  • 优点:因为它不阻塞浏览器的绘制过程,所以不会导致页面卡顿或白屏,用户体验更流畅。
  • 常见用例
    • API 数据获取
    • 设置订阅或事件监听器
    • 手动操作 DOM(如添加动画类),但这些操作不会影响页面的初始布局计算。
jsx
import { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  // ✅ Good: useEffect for data fetching or non-blocking side effects.
  useEffect(() => {
    console.log('useEffect: Count updated!', count);
    document.title = `You clicked ${count} times`;
    
    // Cleanup function (e.g., unsubscribe)
    return () => {
      console.log('Cleanup effect');
    };
  }, [count]);

  return <button onClick={() => setCount(c => c + 1)}>Click me</button>;
}

2. useLayoutEffect - “事前干预”

  • 工作原理:当函数组件进行渲染后,在浏览器真正将像素点画到屏幕上之前,React会同步地调用 useLayoutEffect的回调函数。它会阻塞后续的绘制流程
  • 缺点:如果回调函数中包含耗时操作,会导致明显的延迟感,用户可能会看到空白页或卡顿。
  • 适用场景:当你需要根据最新的 DOM 布局信息来做一些必须立即生效且不能让用户看到的“闪动”的操作时。
    • 测量 DOM:比如获取元素的宽度、高度、滚动位置等。
    • 根据测量结果立即修改样式或状态:以避免用户看到中间的不一致状态。
jsx
import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
  const ref = useRef();
  
   // ⚠️ Use case for useLayoutEffect: Measuring layout and synchronously updating state.
   // Prevents flicker by applying changes before the browser paints.
   useLayoutEffect(() => {
     const { width } = ref.current.getBoundingClientRect();
     if (width > window.innerWidth) {
       // If the element is too wide, we adjust its style immediately.
       // This happens BEFORE the user sees the original oversized element.
       ref.current.style.transform = 'scale(0.8)';
     }
   });

   return <div ref={ref}>I might be a very long tooltip that overflows!</div>;
}

“闪动”问题的经典示例

假设我们有一个需求:点击按钮切换一个 div,并且希望它从顶部出现。

  • 错误使用 useEffect(可能导致闪动):

    1. 组件因状态改变而重新渲染,<div>的位置通过 CSS(position: absolute; top: ?px)决定。
    2. 由于状态变化,<div>的初始位置可能是错误的(比如还在屏幕外)。
    3. 浏览器开始绘制这个“错误”的画面(用户可能瞥见一瞬间的跳动)。
    4. 然后,useEffect中的回调才运行,计算出正确位置并更新状态/样式。
    5. 组件再次重新渲染并显示正确位置。(用户看到了两次画面)
  • 正确使用 useLayoutEffect(无闪动):

    1. 组件因状态改变而重新渲染,<div>的初始位置同样是错误的。
    2. 在浏览器即将绘制前,同步地运行 useLayoutEffect。它读取 DOM(此时还是旧状态),计算出正确的新位置和样式,直接写入到 DOM(或通过更新状态触发一次新的同步重绘)。这次重绘发生在当前帧内。
    3. 然后浏览器才开始绘制,直接画出最终的正确效果。用户看不到中间的错误状态。

React Team的建议与总结

“If you’re migrating code from a class component, note that useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate. However, we recommend starting with [useEffect] first and only trying [useLayoutEffect] if it turns out you need it.”

—— React官方文档建议:

  1. 默认使用 useEffect。它的性能更好,是现代 React Apps的首选。
  2. 只有在遇到可见问题(如闪烁)时,才考虑将特定的副作用迁移到 useLayoutEffect
  3. 绝大多数情况下你不需要用到它。useEffect能覆盖99%的用例。

简单来说:先尝试用 useEffect,如果发现因为布局和绘制的时序问题导致了视觉上的 bug(尤其是闪烁),再换用 useLayoutEffect

00:00
00:00