001-React生命周期

以下是React生命周期的详细解析,包含具体应用场景、变更原因及图示说明,基于React 17/18版本(截至2025年6月)。


一、React生命周期全阶段图解与说明

新版生命周期流程图(React 16.4+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph TD
A[挂载阶段] --> B(constructor)
B --> C{static getDerivedStateFromProps}
C --> D(render)
D --> E(componentDidMount)

F[更新阶段] --> C
C --> G{shouldComponentUpdate}
G -->|true| D
D --> H(getSnapshotBeforeUpdate)
H --> I(componentDidUpdate)

J[卸载阶段] --> K(componentWillUnmount)

L[错误处理] --> M(static getDerivedStateFromError)
M --> N(componentDidCatch)

二、各阶段详解与应用场景

1. 挂载阶段(Mounting)

  • constructor()

    • 场景:初始化state(如表单默认值)、绑定事件方法
    • 示例
      1
      2
      3
      4
      5
      constructor(props) {
      super(props);
      this.state = { count: 0 };
      this.handleClick = this.handleClick.bind(this);
      }
  • static getDerivedStateFromProps(props, state)

    • 场景:props变化时同步state(如分页组件接收外部页码参数)
    • 限制:必须是纯函数,无副作用
    • 示例
      1
      2
      3
      static getDerivedStateFromProps(props) {
      return { page: props.initialPage }; // 用props初始化state
      }
  • render()

    • 规则:返回JSX,禁止执行setState或操作DOM
  • componentDidMount()

    • 核心场景
      1. 数据请求(AJAX/Fetch)
      2. DOM操作(集成D3.js图表)
      3. 订阅事件(WebSocket监听)
    • 示例
      1
      2
      3
      4
      componentDidMount() {
      fetch('/api/data').then(res => this.setState({ data: res.data }));
      this.timer = setInterval(() => this.tick(), 1000);
      }

2. 更新阶段(Updating)

  • shouldComponentUpdate(nextProps, nextState)

    • 场景:性能优化(阻止不必要的渲染)
    • 示例
      1
      2
      3
      shouldComponentUpdate(nextProps) {
      return nextProps.userID !== this.props.userID; // 仅当userID变化时重渲染
      }
  • getSnapshotBeforeUpdate(prevProps, prevState)

    • 场景:DOM更新前捕获状态(如滚动位置保持)
    • 经典案例:聊天窗口新消息插入时保持视图位置
      1
      2
      3
      4
      5
      6
      7
      getSnapshotBeforeUpdate() {
      return this.listRef.scrollHeight; // 返回更新前容器高度
      }
      componentDidUpdate(prevProps, prevState, snapshot) {
      // snapshot为更新前高度,计算差值调整滚动条
      this.listRef.scrollTop += this.listRef.scrollHeight - snapshot;
      }
  • componentDidUpdate(prevProps)

    • 场景
      1. props变化后重新请求数据(如路由参数变更)
      2. 更新第三方库(如图表数据更新)
        1
        2
        3
        4
        5
        componentDidUpdate(prevProps) {
        if (this.props.productID !== prevProps.productID) {
        this.fetchProductData(); // ID变化时重新拉取数据
        }
        }

3. 卸载与错误处理

  • componentWillUnmount()

    • 场景:清理定时器/事件监听/取消请求
      1
      2
      3
      4
      componentWillUnmount() {
      clearInterval(this.timer);
      window.removeEventListener('resize', this.handleResize);
      }
  • 错误边界(Error Boundaries)

    • **static getDerivedStateFromError()**:渲染备用UI
    • **componentDidCatch()**:记录错误日志
    • 示例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      class ErrorBoundary extends Component {
      state = { hasError: false };
      static getDerivedStateFromError() {
      return { hasError: true }; // 触发降级UI
      }
      componentDidCatch(error, info) {
      logErrorToService(error, info); // 上报错误
      }
      }

三、生命周期变更的核心原因

1. Fiber架构的异步渲染需求

React 16引入Fiber架构以实现可中断渲染,旧生命周期(如componentWillMount)可能被多次调用,导致:

  • 资源泄露:重复创建订阅/请求
  • 状态不一致:中断后恢复时props/state不同步

2. 副作用规范化

  • 问题componentWillMount中的异步操作可能操作未挂载的DOM
  • 解决:将副作用(数据请求等)移至componentDidMount,保证DOM就绪

3. 不安全方法的滥用

  • componentWillReceiveProps反模式
    1
    2
    3
    4
    // 旧版:易错误同步props到state
    componentWillReceiveProps(nextProps) {
    this.setState({ value: nextProps.value }); // 破坏单一数据源
    }
    新版通过**getDerivedStateFromProps**强制纯函数解决

四、Hooks对生命周期的替代方案

函数组件+Hooks成为主流范式(React 16.8+):

类组件生命周期 Hooks等效方案 示例
componentDidMount useEffect(..., []) 初始化请求数据
componentDidUpdate useEffect(..., [deps]) 依赖项更新后执行逻辑
componentWillUnmount useEffect返回清理函数 清除定时器/订阅
shouldComponentUpdate React.memo + useMemo 浅比较props避免重渲染
getDerivedStateFromProps useState + useEffect 根据props更新state

Hooks优势:逻辑复用(自定义Hook)、无this绑定问题、代码更简洁


五、关键场景对比表

场景 类组件方案 函数组件方案
数据请求 componentDidMount useEffect + 空依赖数组
滚动位置保持 getSnapshotBeforeUpdate useLayoutEffect + ref保存位置
事件订阅/清理 componentDidMount + componentWillUnmount useEffect返回清理函数
props派生state getDerivedStateFromProps useState + useEffect监视props
错误捕获 getDerivedStateFromError 暂无直接等效(需类组件错误边界)

六、官方变更总结与最佳实践

  1. 废弃原因

    • 🚫 componentWillMount:服务端渲染可能重复调用
    • 🚫 componentWillReceiveProps:易错误同步props/state
    • 🚫 componentWillUpdate:操作DOM导致状态不一致
  2. 迁移策略

    • 新项目:函数组件 + Hooks
    • 旧项目改造:
      • getDerivedStateFromProps替代componentWillReceiveProps
      • getSnapshotBeforeUpdate替代componentWillUpdate
  3. 设计思想演进

    • 同步 → 异步:为Concurrent Mode提供中断/优先级调度能力
    • 副作用隔离:渲染阶段保持纯净,副作用集中到Commit阶段
    • 函数式优先:Hooks推动函数式编程,简化组件逻辑

附:生命周期完整对比图(新旧版本)
React生命周期对比图(来源:React官方社区图表)