关于React hooks

53 阅读3分钟

useEffect vs useLayoutEffect

执行时机「关键区别」

钩子触发时机
useEffect异步执行:在浏览器完成绘制paint延迟执行,不阻塞页面渲染
useLayoutEffect同步执行:在DOM更新后、浏览器绘制前立即执行,会阻塞渲染

✅ 执行流程图示
DOM 更新 → useLayoutEffect 执行 → 浏览器绘制 → useEffect 执行

使用场景

钩子适用场景不适用场景
useEffect- 数据获取
- 订阅事件
- 手动操作全局对象如window
- 无需同步更新DOM的操作
需要根据DOM计算并同步更新样式/布局
useLayoutEffect- 读取/修改DOM布局(如元素尺寸、位置)
- 避免视觉闪烁(如调整元素样式)
- 同步更新状态以影响渲染
耗时操作(会阻塞渲染)

性能影响

  • useEffect: 异步执行,不会阻塞渲染,适合大多数副作用操作
  • useLayoutEffect: 会阻塞浏览器渲染,避免在内部执行耗时操作(如大量计算、网络请求等),否则会导致页面卡顿

服务端渲染SSR

  • useEffect: 在服务端渲染中可安全使用
  • useLayoutEffect:
    在SSR中会触发React警告(因为服务端无DOM)。解决方案:
// 在SSR中降级为useEffect
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect :useEffect;

代码示例

const Component = () => {
  const [width, setWidth] = useState(0);
  const divRef = useRef();
  
  // 可能闪烁
  useEffect(() => {
    // 此时浏览器已绘制,用户可能先看到0px 再看到100px
    setWidth(divRef.current.offsetWidth);
  }, []);
  
  // 无闪烁
  useLayoutEffect(() => {
    // 在绘制前同步更新,用户直接看到100px
    setWidth(divRef.current.offsetWidth);
  }, []);
  
  return <div ref={divRef}>Width: {width}px</div>;
}

总结:如何选择?

根据经验,默认优先使用useEffect;仅当需要同步测量/修改DOM且遇到视觉问题时,才改用useLayoutEffect


为什么hook不能写在条件语句里?

React内部通过调用顺序来跟踪每个hook的状态。如果我们将hook放在条件语句、循环/嵌套函数中,可能会导致hook的调用顺序在渲染之间发生变化,从而引发难以追踪的bug。

React Hooks必须无条件、按固定顺序调用,这是由React内部实现机制决定的。根本原因在于React依赖Hook的调用顺序来跟踪状态,具体原理如下:

顺序索引跟踪状态「核心原因」

React内部使用调用顺序索引来关联hook及其状态。每次渲染时,React会:

  1. 按顺序创建一个hook状态链表
  2. 用索引值(如0,1,2,...)标识每个hook
  3. 下次渲染时严格依赖相同的调用顺序匹配状态

条件语句如何破坏状态?

当hook放在条件语句中时,会导致调用顺序在不同渲染间不一致

const Component = () => {
  if (condition) {
    useEffect(..);  // index 0
  }
  
  useEffect(..);  // index 0/1
  useEffect(..);  // index 1/2
};

破坏原理

  1. 状态错位
    第二个useEffec在condition为false时变成索引0,错误匹配了第一次渲染索引0的状态
  2. 状态丢失
    条件跳过的Hook会导致后续所有Hook索引偏移
  3. 副作用错乱
    useEffect/useLayoutEffect的清理和触发时机可能错配

正确方案

将条件判断移到Hook内部:

useEffect(() => {
    if (condition) {
        // ..
    }
}, [condition]);

React的防护机制

开发模式下React会:

  • 检测渲染间Hook数量变化
  • 抛出错误:Rendered fewer/more hooks than expected
  • 提示:React Hookd must be called in the exact same order

优势

  • 状态确定性:组件行为可预测
  • 自动清理:保证副作用清理函数可靠执行

所有Hook调用必须无条件地置于组件顶层。