MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React组件的生命周期详解

2021-06-022.0k 阅读

React 组件生命周期的基本概念

在 React 开发中,组件的生命周期指的是组件从被创建到被销毁的整个过程。React 为组件提供了一系列生命周期方法,这些方法在组件生命周期的特定阶段被自动调用,开发者可以在这些方法中编写自定义逻辑,以实现组件在不同阶段的功能需求。例如,在组件创建时进行数据的初始化,在组件更新时处理状态变化,在组件销毁时清理相关资源等。

React 组件的生命周期可以分为三个主要阶段:挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting)。每个阶段都有相应的生命周期方法可供使用。

挂载阶段(Mounting)

挂载阶段是组件被创建并插入到 DOM 中的过程。在这个阶段,React 会依次调用以下生命周期方法:

constructor()

constructor() 是 ES6 类的构造函数,在 React 组件中,它是组件实例化时最先被调用的方法。通常在 constructor() 中进行以下操作:

  • 初始化 state:通过 this.state 来初始化组件的状态。
  • 绑定事件处理函数:如果组件中有需要绑定到实例的事件处理函数,可以在 constructor() 中使用 bind 方法进行绑定。

以下是一个简单的示例:

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({
      count: this.state.count + 1
    });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,constructor() 接受 props 参数,并通过 super(props)props 传递给父类的构造函数。然后初始化了 state 中的 count 为 0,并将 handleClick 方法绑定到组件实例。

getDerivedStateFromProps(nextProps, prevState)

getDerivedStateFromProps 是一个静态方法,它在组件挂载和更新时都会被调用。该方法的作用是根据 props 的变化来更新 state。它接收两个参数:nextProps 表示即将更新的 propsprevState 表示之前的 state

例如,假设有一个组件根据传入的 props 中的 initialCount 来初始化 state 中的 count,并且当 props 中的 initialCount 变化时,state 中的 count 也需要相应更新:

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.initialCount
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.initialCount!== prevState.count) {
      return {
        count: nextProps.initialCount
      };
    }
    return null;
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
      </div>
    );
  }
}

export default MyComponent;

在这个例子中,当 props 中的 initialCount 发生变化时,getDerivedStateFromProps 方法会根据新的 initialCount 更新 state 中的 count。如果不需要更新 state,则返回 null

render()

render() 方法是 React 组件中唯一必需的方法。它负责返回描述组件 UI 的 React 元素。render() 方法应该是一个纯函数,即它不应该修改组件的 state,也不应该与浏览器 API 进行交互。

以下是一个简单的 render() 方法示例:

import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return (
      <div>
        <h1>Hello, React!</h1>
      </div>
    );
  }
}

export default MyComponent;

在这个例子中,render() 方法返回一个包含标题 "Hello, React!" 的 div 元素。

componentDidMount()

componentDidMount() 方法在组件被插入到 DOM 后立即调用。这个方法通常用于执行需要访问 DOM 的操作,例如初始化第三方库、进行网络请求等。

例如,使用 axios 库进行网络请求并更新组件状态:

import React, { Component } from 'react';
import axios from 'axios';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  componentDidMount() {
    axios.get('https://example.com/api/data')
    .then(response => {
        this.setState({
          data: response.data
        });
      })
    .catch(error => {
        console.error('Error fetching data:', error);
      });
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.data.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,componentDidMount() 方法在组件挂载后执行网络请求,获取数据并更新 state,然后 render() 方法根据更新后的 state 渲染数据列表。

更新阶段(Updating)

当组件的 propsstate 发生变化时,组件会进入更新阶段。在这个阶段,React 会依次调用以下生命周期方法:

getDerivedStateFromProps(nextProps, prevState)

如前文所述,getDerivedStateFromProps 在更新阶段也会被调用,用于根据 props 的变化来更新 state

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate() 方法在组件接收到新的 propsstate 时被调用,它允许开发者根据当前和即将更新的 propsstate 来决定组件是否需要更新。该方法接收两个参数:nextPropsnextState,分别表示即将更新的 propsstate

如果 shouldComponentUpdate() 返回 true,则组件会继续更新流程;如果返回 false,则组件不会更新,render() 方法也不会被调用。这可以有效地提高性能,避免不必要的重新渲染。

例如,假设一个组件只有在 props 中的某个特定属性变化时才需要更新:

import React, { Component } from 'react';

class MyComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.specificProp!== this.props.specificProp;
  }

  render() {
    return (
      <div>
        <p>{this.props.specificProp}</p>
      </div>
    );
  }
}

export default MyComponent;

在这个例子中,只有当 props 中的 specificProp 发生变化时,shouldComponentUpdate() 才会返回 true,组件才会更新。

render()

在更新阶段,render() 方法会再次被调用,以根据更新后的 propsstate 重新渲染组件。

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 方法在 render() 之后,componentDidUpdate() 之前被调用。它的主要作用是在 DOM 更新之前捕获一些信息,例如滚动位置、元素尺寸等。该方法接收两个参数:prevPropsprevState,分别表示更新前的 propsstate

例如,假设一个可滚动的列表组件,当列表数据更新时,希望保持当前的滚动位置:

import React, { Component } from 'react';

class ScrollableList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: []
    };
    this.listRef = React.createRef();
  }

  componentDidMount() {
    // 模拟数据获取
    this.setState({
      items: ['item1', 'item2', 'item3']
    });
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.items.length!== this.state.items.length) {
      const list = this.listRef.current;
      return list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot!== null) {
      const list = this.listRef.current;
      list.scrollTop = snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef} style={{ height: '200px', overflowY: 'auto' }}>
        {this.state.items.map((item, index) => (
          <p key={index}>{item}</p>
        ))}
      </div>
    );
  }
}

export default ScrollableList;

在上述代码中,getSnapshotBeforeUpdate() 方法在列表数据更新前获取当前的滚动位置,componentDidUpdate() 方法在 DOM 更新后恢复滚动位置。

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() 方法在组件更新完成后被调用。它接收三个参数:prevPropsprevState 分别表示更新前的 propsstatesnapshotgetSnapshotBeforeUpdate() 方法返回的值(如果有)。

componentDidUpdate() 中可以执行一些依赖于 DOM 更新后的操作,例如操作更新后的 DOM、进行额外的网络请求等。

例如,当 props 中的某个属性变化时,重新进行网络请求:

import React, { Component } from 'react';
import axios from 'axios';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps) {
    if (this.props.someProp!== prevProps.someProp) {
      this.fetchData();
    }
  }

  fetchData() {
    axios.get(`https://example.com/api/data?param=${this.props.someProp}`)
    .then(response => {
        this.setState({
          data: response.data
        });
      })
    .catch(error => {
        console.error('Error fetching data:', error);
      });
  }

  render() {
    return (
      <div>
        <ul>
          {this.state.data.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default MyComponent;

在这个例子中,当 props 中的 someProp 发生变化时,componentDidUpdate() 方法会触发重新获取数据的操作。

卸载阶段(Unmounting)

当组件从 DOM 中移除时,会进入卸载阶段。此时会调用 componentWillUnmount() 方法。

componentWillUnmount()

componentWillUnmount() 方法在组件即将从 DOM 中移除时被调用。这个方法通常用于清理在组件挂载过程中创建的资源,例如取消网络请求、清除定时器等。

例如,当组件中有一个定时器时,需要在组件卸载时清除定时器:

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.timer = null;
  }

  componentDidMount() {
    this.timer = setInterval(() => {
      this.setState({
        count: this.state.count + 1
      });
    }, 1000);
  }

  componentWillUnmount() {
    if (this.timer) {
      clearInterval(this.timer);
    }
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,componentDidMount() 方法启动了一个定时器,每秒更新一次 state 中的 countcomponentWillUnmount() 方法在组件卸载时清除了定时器,避免内存泄漏。

React 16.3 之后新增的生命周期方法及变化

在 React 16.3 版本中,引入了一些新的生命周期方法,同时对部分旧的生命周期方法进行了废弃或调整。

新增的生命周期方法

  • getDerivedStateFromProps(nextProps, prevState):如前文所述,这个静态方法在组件挂载和更新时都会被调用,用于根据 props 的变化来更新 state。它取代了之前在 componentWillMountcomponentWillReceiveProps 中进行的一些 state 更新逻辑。
  • getSnapshotBeforeUpdate(prevProps, prevState):在 DOM 更新之前捕获信息,配合 componentDidUpdate 使用,用于处理一些依赖于 DOM 更新前状态的操作。

废弃或调整的生命周期方法

  • componentWillMount:在 React 16.3 及以后版本中,该方法被标记为不安全的生命周期方法。因为在 componentWillMount 中执行的操作可能会在 React 未来的异步渲染模式下被重复执行。如果需要在组件挂载时执行一些操作,可以将逻辑移到 constructorcomponentDidMount 中。
  • componentWillReceiveProps(nextProps):该方法在 React 16.3 及以后版本中也被标记为不安全的生命周期方法。其功能可以通过 getDerivedStateFromPropscomponentDidUpdate 来实现。getDerivedStateFromProps 用于根据 props 变化更新 statecomponentDidUpdate 用于在 props 变化后执行副作用操作。
  • componentWillUpdate(nextProps, nextState):同样在 React 16.3 及以后版本中被标记为不安全的生命周期方法。可以使用 getSnapshotBeforeUpdatecomponentDidUpdate 来替代它的功能。getSnapshotBeforeUpdate 用于捕获 DOM 更新前的状态,componentDidUpdate 用于在 DOM 更新后执行操作。

总结 React 组件生命周期的使用场景和注意事项

使用场景

  • 初始化操作:在 constructor 中初始化 state 和绑定事件处理函数,在 componentDidMount 中进行网络请求、初始化第三方库等操作。
  • 响应 props 变化:使用 getDerivedStateFromProps 根据 props 变化更新 state,在 componentDidUpdate 中执行依赖于 props 变化的副作用操作。
  • 性能优化:通过 shouldComponentUpdate 方法来控制组件是否需要更新,避免不必要的重新渲染,提高应用性能。
  • 清理资源:在 componentWillUnmount 中清理定时器、取消网络请求等,防止内存泄漏。

注意事项

  • 避免在 render 方法中修改 staterender 方法应该是一个纯函数,不能直接修改 state,否则会导致不可预测的行为。
  • 谨慎使用废弃的生命周期方法:随着 React 版本的发展,一些生命周期方法已被标记为不安全或废弃,应尽量使用新的生命周期方法来编写组件,以确保应用的兼容性和性能。
  • 注意 state 更新的时机:在使用 setState 更新 state 时,要注意它是异步的。如果需要在 state 更新后执行某些操作,可以使用 setState 的回调函数或在 componentDidUpdate 中处理。

通过深入理解 React 组件的生命周期及其各个阶段的方法,开发者可以更加灵活和高效地编写 React 组件,实现复杂的交互逻辑和优化应用性能。