为什么 React 要 “淘汰” 旧生命周期?从原理到实践,一文读懂钩子函数的迭代逻辑

315 阅读7分钟

React新旧生命周期详解:从原理到实践

一、React生命周期概述

React组件的生命周期是指组件从创建到卸载的整个过程,它提供了多个钩子函数让开发者能够在不同阶段执行特定操作。随着React版本的迭代,生命周期API发生了重要变化,尤其是在React 16.3版本中引入了新的生命周期方法,同时标记了部分旧方法为不安全(UNSAFE_)。

二、旧版生命周期(React 16.3之前)

1. 生命周期执行顺序

旧版生命周期主要分为三个阶段:初始化阶段、更新阶段和卸载阶段。

初始化阶段(由ReactDOM.render()触发)

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount() ===> 常用 更新阶段(由this.setState()或父组件render触发)

更新阶段(由this.setState()或父组件render触发)

  1. componentWillReceiveProps()
  2. shouldComponentUpdate()
  3. componentWillUpdate()
  4. render() ===> 必须使用
  5. componentDidUpdate()

卸载阶段(由ReactDOM.unmountComponentAtNode()触发)

  1. componentWillUnmount() ===> 常用

屏幕截图 2025-07-07 232400.png

旧生命周期图

image.png

2.旧生命周期代码示例

三、新版生命周期(React 16.3及以后)

1. 生命周期执行顺序

新版生命周期引入了两个新的生命周期方法,并对三个旧方法添加了UNSAFE_前缀。

挂载阶段

  1. constructor()
  2. static getDerivedStateFromProps()
  3. render()
  4. componentDidMount()

更新阶段

  1. static getDerivedStateFromProps()
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate()

卸载阶段

  1. componentWillUnmount()

image.png

新生命周期图

image.png

2. 新生命周期代码示例

派生状态的作用

派生状态主要用于以下场景:

  1. 初始化状态:基于 props 初始化 state
  2. 响应 props 变化:当 props 变化时更新 state
  3. 数据格式化:将 props 数据转换为特定格式

四、为什么要弃用旧的生命周期钩子

新生命周期改动:(带Will的要改,除了componentWillUnmount)

componentWillMount → UNSAFE_componentWillMount

componentWillReceiveProps → UNSAFE_componentWillReceiveProps

componentWillUpdate → UNSAFE_componentWillUpdate

为什么更改,官网给出答案:(不是核心的钩子)

React团队在致力于实现异步渲染的过程中发现,某些旧的生命周期方法容易导致不安全的编码实践。根据官方文档,以下方法被标记为不安全:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate 这些方法被弃用的主要原因包括:
  1. 异步渲染导致的问题 :在异步渲染模式下,这些生命周期可能会被调用多次,或者在渲染之前被取消,容易导致不一致的状态和内存泄漏。
  2. 滥用风险 :这些方法经常被误解和滥用。例如,开发者可能会在componentWillMount中进行数据获取,这在服务端渲染中会导致问题,并且在异步渲染中可能被调用多次。
  3. 更好的替代方案 :新的生命周期提供了更安全、更可预测的方式来实现相同的功能。getDerivedStateFromProps替代了componentWillReceiveProps,getSnapshotBeforeUpdate替代了componentWillUpdate的部分使用场景。 React团队在这些方法名称前添加了"UNSAFE_"前缀,明确表示使用这些生命周期的代码在React的未来版本中更有可能出现bug,尤其是在启用异步渲染之后。

五、新生命周期方法详解

1. static getDerivedStateFromProps(props, state)

  • 调用时机 :在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • 返回值 :返回一个对象来更新state,如果返回null则不更新任何内容。
  • 适用场景 :state的值在任何时候都取决于props的情况。
  • 注意事项 :
    • 这是一个静态方法,无权访问组件实例(this)
    • 每次渲染前都会触发,而不仅是在props更改时
    • 如果需要执行副作用(如数据提取或动画),应该使用componentDidUpdate
    • 如果只需要在prop更改时重新计算某些数据,考虑使用memoization helper
    • 如果想在prop更改时"重置"某些state,考虑使组件完全受控或使用key使组件完全不受控

2. getSnapshotBeforeUpdate(prevProps, prevState)

  • 调用时机 :在render之后,DOM更新之前调用。
  • 返回值 :返回一个快照值(任何类型),该值将作为componentDidUpdate的第三个参数。
  • 适用场景 :需要在DOM更新前捕获一些信息(如滚动位置)的场景。
  • 注意事项 :
    • 此方法的返回值必须与componentDidUpdate配合使用
    • 典型用例是在列表滚动时保持滚动位置

六、新旧生命周期对比总结

主要变化

  1. 移除的方法 :componentWillMount、componentWillReceiveProps、componentWillUpdate (仍可使用但添加了UNSAFE_前缀)
  2. 新增的方法 :getDerivedStateFromProps、getSnapshotBeforeUpdate
  3. 保留的方法 :constructor、render、componentDidMount、shouldComponentUpdate、componentDidUpdate、componentWillUnmount

最佳实践建议

  1. 数据获取 :应在componentDidMount中进行,而不是componentWillMount
  2. 状态派生 :使用getDerivedStateFromProps替代componentWillReceiveProps
  3. DOM操作前准备 :使用getSnapshotBeforeUpdate替代componentWillUpdate
  4. 清理工作 :始终在componentWillUnmount中进行,如清除定时器、取消订阅等
  5. 性能优化 :合理使用shouldComponentUpdate或React.memo避免不必要的渲染

迁移策略

  • 对于componentWillMount:将其内容移至componentDidMount
  • 对于componentWillReceiveProps:使用getDerivedStateFromProps替代
  • 对于componentWillUpdate:使用getSnapshotBeforeUpdate替代

七、总结

React生命周期的更新反映了React团队对异步渲染的重视,以及对更安全、更可预测的组件行为的追求。虽然旧的生命周期方法仍然可以使用(带有UNSAFE_前缀),但建议新的项目采用新的生命周期API,以避免未来可能出现的兼容性问题。

理解并正确使用生命周期方法是编写高质量React组件的关键。新的生命周期API虽然增加了一些学习成本,但它们提供了更明确的职责划分和更安全的使用方式,有助于构建更健壮、更易于维护的React应用。

经典面试题

1.React组件的生命周期分为哪些阶段?各阶段包含哪些主要方法?

答案 : React组件生命周期分为三个主要阶段:

  1. 挂载阶段(Mounting) :组件从创建到首次渲染到DOM的过程

    • constructor() :初始化state和绑定事件处理函数
    • static getDerivedStateFromProps(props, state) :根据props更新state(React 16.3+新增)
    • render() :渲染虚拟DOM
    • componentDidMount() :组件挂载后调用,适合执行副作用操作(如数据请求、订阅事件)
  2. 更新阶段(Updating) :组件props或state变化时触发的重新渲染过程

    • static getDerivedStateFromProps(props, state) :同上
    • shouldComponentUpdate(nextProps, nextState) :决定是否重新渲染,返回布尔值
    • render() :重新渲染
    • getSnapshotBeforeUpdate(prevProps, prevState) :在DOM更新前获取快照(如滚动位置)
    • componentDidUpdate(prevProps, prevState, snapshot) :更新后调用,可执行DOM操作或数据请求
  3. 卸载阶段(Unmounting) :组件从DOM中移除的过程

    • componentWillUnmount() :执行清理操作(如取消订阅、清除定时器)

2.React 16.3+为什么废弃componentWillMount、componentWillReceiveProps和componentWillUpdate?替代方案是什么?

答案 : 废弃原因 :

  1. 异步渲染兼容性问题 :React 16引入Fiber架构支持异步渲染,这些生命周期可能在渲染过程中被多次调用,导致副作用(如数据请求)重复执行 。
  2. 副作用管理风险 :开发者常在此类方法中执行数据请求或状态更新,可能导致不可预测的渲染结果 。
  3. API简化 :新生命周期提供更明确的职责划分,避免滥用。

替代方案 :

image.png

示例代码 :

// 替代componentWillReceiveProps的静态方法
// 作用:根据新传入的props计算并返回新的state
// 注意:这是静态方法,不能使用this关键字,必须返回对象或null
static getDerivedStateFromProps(nextProps, prevState) {
  // 比较新props中的userId与当前state中的userId是否不同
  if (nextProps.userId !== prevState.userId) {
    // 如果不同,返回新的state对象更新userId
    return { userId: nextProps.userId };
  }
  // 如果相同,返回null表示不需要更新state
  return null;
}

// 组件更新完成后的生命周期方法
// 作用:执行依赖于DOM更新的副作用操作(如数据请求、DOM操作)
// 参数prevProps:更新前的props
componentDidUpdate(prevProps) {
  // 比较当前props与更新前的props中的userId是否不同
  if (this.props.userId !== prevProps.userId) {
    // 如果不同,调用数据请求方法获取新用户数据
    this.fetchUserData(this.props.userId); // 副作用操作(数据请求、订阅等)
  }
}