React 生命周期函数全解析

187 阅读9分钟

React 的生命周期函数在组件的不同阶段发挥着至关重要的作用,深入理解这些函数能够帮助开发者更好地开发和优化 React 应用。下面我们来详细介绍几个重要的生命周期函数。

constructor

在 React 中,constructor 主要有以下几个作用:

  1. 初始化 state。例如在代码中可以这样写,这里初始化了一个名为counterstate,初始值为 0
constructor(props){
  super(props);
  this.state = { counter:0 };
}
  1. 纠正方法的 this 指向。可以通过this.handleClick = this.handleClick.bind(this);这样的方式将事件绑定在当前组件上,确保在事件处理函数中能够正确访问组件的属性和方法。如果使用 ES6 箭头函数,将不需要将事件在 constructor 中改变 this 指向,如<button onClick={() => this.click()}>纠正 this 指向</button>
  2. 如果有防抖、截流,也可以在这里做

getDerivedStateFromProps(静态方法)

getDerivedStateFromProps 是一个静态方法,在 React 组件的生命周期里承担着从props派生state的职责。React 将其设计为静态方法,旨在使其成为一个纯函数。这就意味着它不能有副作用,例如修改外部变量、发起网络请求等。每次调用这个函数时,都应仅依据传入的props和当前的state返回一个新的state对象,并且相同的输入必须始终产生相同的输出。例如:

statice getDerivedStateFromProps(newProps) {
  const { type } = newProps;
  switch(type) {
    case 'age':
      return { name: '年龄', defaultValue: 18 };
    case 'sex':
      return { name: '性别', defaultValue: '男' };
  }}

这个方法会在组件挂载(mount)以及每次接收新的props导致组件更新时被调用。它在render方法之前执行,这使得组件可以根据新的props来调整自己的state,从而影响渲染结果。

与其他生命周期的关系

componentWillReceiveProps

在 React 16.3 版本之前,componentWillReceiveProps常用于在组件接收新props时更新statecomponentWillReceiveProps可以访问this.propsthis.state,并且可以执行副作用操作,所以这个方法容易被滥用,导致一些难以发现的错误和性能问题;而getDerivedStateFromProps是一个静态方法,不能访问this,并且必须是纯函数,这样就限制了它的行为,使得组件的更新逻辑更加可预测。

shouldComponentUpdate

在组件更新流程中,getDerivedStateFromPropsshouldComponentUpdate之前被调用。它的返回值会与当前state合并后作为参数传递给shouldComponentUpdate,这使得shouldComponentUpdate可以基于更新后的state来决定组件是否需要真正进行重新渲染,从而在一定程度上优化组件的性能。

componentWillReceiveProps

componentWillReceiveProps是一个在 React 组件生命周期中比较重要的方法。它在组件接收到新的props时被调用,这发生在组件已经挂载(mounted)之后。在 React 16.3 版本之前,它是更新props时常用的生命周期钩子。

从功能上来说,它允许组件在接收到新的props后,根据新props和旧props的对比情况,来决定是否需要更新组件的state,从而触发重新渲染。

这个生命周期方法在组件已经挂载到 DOM 中,并且接收到新的props时触发。需要注意的是,这个方法在组件首次挂载时不会被调用,只有在后续props发生变化时才会执行。它接收一个参数,即新的props对象。在方法内部,可以通过this.props访问旧的props,这样就可以比较新旧props的差异。

举个例子:

constructor(props) {
  super(props);
  this.state = {
    count: props.initialCount
  };
}
componentWillReceiveProps(nextProps) {
  if (nextProps.initialCount!== this.props.initialCount) {
    this.setState({
      count: nextProps.initialCount
    });
  }
}

componentWillUpdate

这个生命周期在组件接收到新的props或者state变化后,重新渲染之前被调用。这个方法提供了一个在组件更新之前执行某些操作的机会。当组件因为propsstate的改变而需要重新渲染时,componentWillUpdate就会被触发。需要注意的是,它不会在组件首次挂载时调用,只在更新阶段起作用。 componentWillUpdate接收两个参数,分别是nextPropsnextStatenextProps代表即将更新的propsnextState代表即将更新的state。通过这两个参数,可以获取更新后的组件属性和状态信息。

使用场景

  • 记录组件更新前的状态:可以利用componentWillUpdate来记录组件更新前的propsstate,用于后续的比较或者其他用途。例如,在调试复杂的组件更新逻辑或者实现撤销 / 重做功能时,这种记录非常有用。
  • 准备更新相关的数据或操作:在组件更新之前,可以在这个方法中进行一些准备工作。比如,如果组件中有一些基于propsstate计算得到的数据,而这些数据的计算成本较高,可以在componentWillUpdate中提前计算,以便在render阶段直接使用,提高渲染效率。不过需要注意的是,在这个方法中不应该调用setState,因为这可能会导致无限循环的更新。
  • 与动画效果结合:在一些需要添加动画效果的场景中,componentWillUpdate可以用于触发动画的开始。例如,当组件的大小或者位置因为propsstate的变化而改变时,可以在componentWillUpdate中设置动画的初始状态,然后在componentDidUpdate中根据更新后的状态完成动画的过渡。

与 shouldComponentUpdate 关联

shouldComponentUpdate决定组件是否需要更新,只有当shouldComponentUpdate返回true时,componentWillUpdate才会被触发。这两个方法共同作用来控制组件的更新过程,shouldComponentUpdate侧重于性能优化,通过比较新旧propsstate来避免不必要的更新,而componentWillUpdate侧重于更新前的准备工作。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate是在组件更新前(更确切地说是在最近一次渲染输出(提交到 DOM 节点)之前)被调用,这个方法使得组件能够在更新前获取一些信息,例如 DOM 元素的状态(如滚动位置、元素大小等)。它的主要目的是与componentDidUpdate配合使用,计算形成一个 snapshot 传给 componentDidUpdate,用于在组件更新过程中捕获一些可能会丢失的信息,以实现更平滑的更新体验,比如在更新列表时保留滚动位置等复杂操作。 举个例子:

class ScrollableList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
    this.state = {
      listData: []
    };
  }
  componentDidMount() {
    // 模拟获取数据并更新列表
    this.setState({ listData: [1, 2, 3, 4, 5] });
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (this.listRef.current) {
      return this.listRef.current.scrollTop;
    }
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot!== null) {
      this.listRef.current.scrollTop = snapshot;
    }
  }
  render() {
    return (
      <div ref={this.listRef}>
        <ul>
          {this.state.listData.map((item) => (
            <li key={item}>{item}</li>
          ))}
        </ul>
      </div>
    );
  }
}

componentDidUpdate

componentDidUpdate是在组件完成更新后被调用。这意味着当组件的props或者state发生变化,导致组件重新渲染之后,componentDidUpdate就会执行。每次组件成功更新后都会触发。但需要注意的是,组件首次挂载时不会调用componentDidUpdate,因为它是专门用于处理更新后的操作。

componentDidUpdate接收三个参数,分别是prevProps(更新前的props)、prevState(更新前的state)和snapshotgetSnapshotBeforeUpdate方法返回的值)。这些参数提供了更新前后的对比信息,方便开发者根据变化进行相应的操作。

componentDidMount

componentDidMount是 React 组件生命周期中的一个关键阶段。它在组件被挂载(mounted)到 DOM 后立即被调用,这意味着组件已经完成了首次渲染并且其对应的 DOM 节点已经被插入到文档中。并且只会在组件第一次被添加到 DOM 时触发一次。无论组件是通过直接渲染还是在条件判断、循环等复杂逻辑下被渲染,只要是首次出现在 DOM 中,componentDidMount就会执行。

componentDidMount非常常见的一个用途就是进行网络请求。通常用于获取组件初始化所需的数据,如从服务器获取数据来填充列表、表单等。

还有就是如果组件需要使用第三方插件(如地图插件、富文本编辑器等),componentDidMount是初始化这些插件的理想时机。

与其他生命周期的关系

componentWillMount

在旧版本的 React 中,有componentWillMount这个生命周期方法。它在组件挂载之前被调用,而componentDidMount在挂载之后。componentWillMount在一些场景下可能会导致一些难以调试的问题,并且其功能在很大程度上可以被constructorcomponentDidMount替代,所以 React 逐渐废弃了这个方法。componentDidMount的优势在于可以确保 DOM 元素已经存在,能够安全地进行 DOM 操作和其他依赖 DOM 的操作。

componentDidUpdate

componentDidMount只在组件首次挂载时执行,而componentDidUpdate在每次组件更新后执行。它们都可以用于操作 DOM,但componentDidUpdate更多地用于处理更新后的情况,例如根据更新后的propsstate来调整 DOM 操作。同时,componentDidUpdate可以利用componentDidMount中初始化的一些资源(如订阅的事件、初始化的插件等),在更新后继续使用或调整。

shouldComponentUpdate

shouldComponentUpdate在 React 组件生命周期中主要用于性能优化。它决定了组件在接收到新的props或者state改变时,是否应该进行重新渲染。通过合理地控制组件的重新渲染,可以避免不必要的 DOM 操作和计算,从而提高应用程序的性能。特别是在处理复杂组件树或者频繁更新的组件时,有效地使用shouldComponentUpdate可以显著减少渲染开销。

在组件接收到新的props或者state发生变化时,shouldComponentUpdate会在渲染之前被触发。这个方法在组件的更新流程中起着一个 “关卡” 的作用,决定组件是否能够进入实际的重新渲染阶段。它接收两个参数,nextPropsnextState,分别代表即将更新的propsstate。通过比较nextProps与当前props(通过this.props访问),以及nextState与当前state(通过this.state访问),可以判断组件是否需要更新。

举个例子:

shouldComponentUpdate(nextProps, nextState) {
  // 只有当count的值发生变化时,组件才重新渲染
  return nextState.count!== this.state.count;
}

最后的最后

虽然 React 的生命周期函数曾经为组件的开发和优化提供了强大的支持,我们也通过对各个生命周期函数的深入了解更好地掌握了 React 组件的运行机制,并且在实际开发中合理运用它们来提高应用的性能和用户体验。但是,随着技术的不断发展,React 引入了更加简洁高效的 Hook。Hook 的出现为 React 开发带来了新的变革,让我们能够以更现代化的方式构建应用程序,提高开发效率和代码的可维护性。下一次,让我们再一起深入了解 React Hook 的强大之处。