React 废弃三个生命周期钩子的原因

解释 React 为何废弃 componentWillMount 等钩子,以及如何用 getDerivedStateFromProps 替代

问题

React 为什么要废弃 componentWillMountcomponentWillReceivePropscomponentWillUpdate 这三个生命周期钩子?

解答

废弃时间线

React 16.3 版本:

  • 为这三个钩子添加 UNSAFE_ 前缀
  • 引入新的生命周期钩子 getDerivedStateFromProps

React 17.0 及之后:

  • 完全删除未加前缀的版本
  • 保留 UNSAFE_ 前缀的版本

废弃原因

React 的更新流程分为 render 阶段和 commit 阶段。这三个被废弃的生命周期钩子都在 render 阶段执行。

在 fiber 架构引入之前,render 阶段不可中断。页面复杂时会阻塞渲染。fiber 架构允许低优先级任务的 render 阶段被高优先级任务打断,这带来了新问题:render 阶段的生命周期函数可能被执行多次

如果在这些钩子中执行副作用操作(如发送网络请求),就会导致同一请求被执行多次:

class MyComponent extends React.Component {
  // ❌ 不推荐:可能被执行多次
  componentWillMount() {
    fetch('/api/data').then(/* ... */);
  }
  
  // ❌ 不推荐:可能被执行多次
  componentWillReceiveProps(nextProps) {
    if (nextProps.id !== this.props.id) {
      fetch(`/api/data/${nextProps.id}`).then(/* ... */);
    }
  }
}

替代方案

React 引入静态方法 getDerivedStateFromProps 来替代:

class MyComponent extends React.Component {
  // ✅ 推荐:静态方法,无法访问 this
  static getDerivedStateFromProps(props, state) {
    if (props.id !== state.prevId) {
      return {
        prevId: props.id,
        data: null
      };
    }
    return null;
  }
  
  // 副作用操作放在 componentDidMount 和 componentDidUpdate
  componentDidMount() {
    this.fetchData();
  }
  
  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }
  
  fetchData() {
    fetch(`/api/data/${this.props.id}`).then(/* ... */);
  }
}

getDerivedStateFromProps 是静态方法,无法通过 this 访问组件实例,也就无法发送请求或调用 this.setState。这强制开发者在 render 前只做无副作用的操作,避免生命周期滥用。

关键点

  • fiber 架构允许 render 阶段被打断,导致 render 阶段的生命周期可能执行多次
  • 在可能多次执行的钩子中进行副作用操作(如网络请求)会导致重复执行
  • getDerivedStateFromProps 是静态方法,无法访问实例,强制开发者避免副作用
  • 副作用操作应放在 componentDidMountcomponentDidUpdate 中执行