React Function Component 挂载/更新执行流程

137 阅读4分钟

在 React 中,Function Component 没有传统的生命周期方法,而是通过 Hooks 模拟和替代了 Class 组件的生命周期逻辑。Function Component 的“生命周期”可以理解为 副作用(Effect)的触发时机状态(State)的更新过程


一、Function Component 的“生命周期”阶段

可以将 Function Component 的执行过程划分为以下阶段:

阶段对应 Class 生命周期核心 Hooks 与行为
挂载阶段constructor + componentDidMountuseState 初始化 → 执行函数体 → 同步执行 useLayoutEffect → 异步执行 useEffect
更新阶段componentDidUpdate状态/Props 变化 → 重新执行函数体 → 同步执行 useLayoutEffect → 异步执行 useEffect
卸载阶段componentWillUnmount组件卸载前执行 useEffect 的清理函数

二、Hooks 执行顺序与核心原理

以下是 Function Component 中 Hooks 与副作用的详细执行流程:

1. 挂载阶段(Mounting)

function MyComponent() {
  // 1. 初始化状态(useState)
  const [count, setCount] = useState(0);

  // 2. 同步副作用(useLayoutEffect)
  useLayoutEffect(() => {
    // DOM 更新后同步执行(类似 componentDidMount + componentDidUpdate)
    console.log('useLayoutEffect (mount)');
    return () => {
      // 清理函数(类似 componentWillUnmount)
      console.log('useLayoutEffect cleanup (mount)');
    };
  }, []);

  // 3. 异步副作用(useEffect)
  useEffect(() => {
    // DOM 更新后异步执行(类似 componentDidMount)
    console.log('useEffect (mount)');
    return () => {
      // 清理函数(类似 componentWillUnmount)
      console.log('useEffect cleanup (mount)');
    };
  }, []);

  // 4. 函数体执行(生成 JSX)
  console.log('render');
  return <div>{count}</div>;
}

执行顺序

  1. useState 初始化状态。
  2. 执行函数体逻辑(生成 JSX)。
  3. 同步执行 useLayoutEffect(在浏览器绘制 DOM 前执行)。
  4. 异步执行 useEffect(在浏览器绘制 DOM 后执行)。

2. 更新阶段(Updating)

当状态或 Props 变化时:

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

  useLayoutEffect(() => {
    console.log('useLayoutEffect (update)');
    return () => {
      // 先执行清理函数(类似 componentWillUpdate)
      console.log('useLayoutEffect cleanup (update)');
    };
  }, [count]); // 依赖项变化时触发

  useEffect(() => {
    console.log('useEffect (update)');
    return () => {
      console.log('useEffect cleanup (update)');
    };
  }, [count]);

  console.log('render');
  return <div onClick={() => setCount(count + 1)}>{count}</div>;
}

执行顺序

  1. 函数体重新执行(生成新的 JSX)。
  2. 同步执行 useLayoutEffect 的清理函数(上一次的清理逻辑)。
  3. 同步执行新的 useLayoutEffect
  4. 异步执行上一次 useEffect 的清理函数
  5. 异步执行新的 useEffect

3. 卸载阶段(Unmounting)

当组件从 DOM 中移除时:

function ParentComponent() {
  const [showChild, setShowChild] = useState(true);

  return (
    <div>
      <button onClick={() => setShowChild(false)}>Unmount Child</button>
      {showChild && <MyComponent />}
    </div>
  );
}

执行顺序

  1. 同步执行 useLayoutEffect 的清理函数
  2. 异步执行 useEffect 的清理函数

三、关键 Hook 的“生命周期”行为

1. useEffect

  • 触发时机:DOM 更新后异步执行。
  • 清理函数:在下一次 useEffect 执行前或组件卸载时执行。
  • 依赖项控制:通过第二个参数(依赖数组)决定何时重新执行:
    • 空数组 []:仅在挂载时执行(类似 componentDidMount)。
    • 有依赖项 [a, b]:依赖变化时执行(类似 componentDidUpdate)。
    • 无依赖项:每次渲染后都执行(谨慎使用)。

2. useLayoutEffect

  • 触发时机:DOM 更新后同步执行(在浏览器绘制前)。
  • 典型场景:读取 DOM 布局(如元素尺寸、滚动位置)并同步更新。
  • 注意事项:避免阻塞浏览器绘制,优先使用 useEffect

3. useState / useReducer

  • 状态更新:状态变化会触发函数组件的重新执行(类似 render)。
  • 批量更新:React 会合并多个 setState 调用,优化渲染性能。

4. useMemo / useCallback

  • 优化渲染:缓存计算结果或函数引用,避免无效重渲染(类似 shouldComponentUpdate)。
  • 依赖项控制:依赖变化时才重新计算值或生成新函数。

四、Function Component 生命周期流程图

挂载阶段:
函数体执行 → useLayoutEffect → useEffect

更新阶段:
函数体执行 → useLayoutEffect清理 → useLayoutEffect → useEffect清理 → useEffect

卸载阶段:
useLayoutEffect清理 → useEffect清理

五、与 Class 组件生命周期的对比

Class 生命周期方法Function Component 等效实现
constructoruseState 初始化
componentDidMountuseEffect(fn, [])
componentDidUpdateuseEffect(fn, [deps])
componentWillUnmountuseEffect(() => { return fn; }, [])
shouldComponentUpdateReact.memouseMemo/useCallback 优化
getSnapshotBeforeUpdateuseLayoutEffect + DOM 操作

六、常见问题与最佳实践

  1. 无限循环问题

    • 错误示例:在 useEffect 中修改依赖项状态且未正确设置依赖。
    useEffect(() => {
      setCount(count + 1); // 依赖 count 但未声明,导致无限循环
    }, []); // ❌ 错误:依赖项缺失
    
    • 修复方法:明确依赖项或使用函数式更新。
    useEffect(() => {
      setCount(c => c + 1); // ✅ 函数式更新,无需依赖 count
    }, []);
    
  2. 内存泄漏问题

    • 错误示例:未清理定时器或订阅。
    useEffect(() => {
      const timer = setInterval(() => {}, 1000);
      // ❌ 错误:未返回清理函数
    }, []);
    
    • 修复方法:始终返回清理函数。
    useEffect(() => {
      const timer = setInterval(() => {}, 1000);
      return () => clearInterval(timer); // ✅ 清理定时器
    }, []);
    
  3. 性能优化

    • 使用 React.memo 包裹组件,避免父组件重渲染导致的子组件无效更新。
    • 使用 useMemo 缓存复杂计算结果,使用 useCallback 缓存函数引用。

七、总结

Function Component 的“生命周期”本质上是 副作用和状态变化的时序控制

  • 挂载阶段:初始化状态 → 执行函数体 → 同步副作用 → 异步副作用。
  • 更新阶段:重新执行函数体 → 清理旧副作用 → 执行新副作用。
  • 卸载阶段:清理所有副作用。

通过合理使用 useEffectuseLayoutEffect 和优化 Hooks(如 useMemouseCallback),可以精准控制组件行为,实现高性能、可维护的 React 应用。