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

React 生命周期方法的执行顺序详解

2024-02-027.1k 阅读

React 生命周期概述

React 组件的生命周期就像是一个人的成长历程,从诞生、成长、变化到消亡,每个阶段都有特定的任务和事件发生。React 的生命周期方法为开发者提供了在这些不同阶段执行自定义代码的能力,从而实现各种复杂的功能,比如数据的初始化、DOM 操作、数据获取与更新以及资源释放等。

在 React 中,组件的生命周期可以分为三个主要阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。每个阶段都包含多个生命周期方法,这些方法会在组件的不同状态变化时被自动调用。

挂载阶段(Mounting)

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

constructor

constructor 是 ES6 类的构造函数,在 React 组件中,它是组件实例化时第一个被调用的方法。它主要用于初始化组件的 state 和绑定事件处理函数。

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) 将其传递给父类的构造函数。这一步是必要的,因为它会初始化 this.props。然后,我们在 constructor 中初始化了 state,并将 handleClick 方法绑定到组件实例,确保在方法内部 this 指向正确的组件实例。

getDerivedStateFromProps

getDerivedStateFromProps 是一个静态方法,它在组件挂载和更新时都会被调用。该方法接收 propsstate 作为参数,并返回一个对象来更新 state,或者返回 null 表示不需要更新 state

import React, { Component } from'react';

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

  static getDerivedStateFromProps(props, state) {
    if (props.initialValue!== state.value) {
      return {
        value: props.initialValue
      };
    }
    return null;
  }

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

export default MyComponent;

在这个例子中,getDerivedStateFromProps 方法会在 props.initialValue 发生变化时更新 state 中的 value。它常用于根据 props 的变化来更新 state,但要注意避免过度使用,因为它可能会导致不必要的 state 更新。

render

render 方法是 React 组件中唯一必需的方法。它根据组件的 propsstate 返回一个 React 元素,描述了组件应该如何渲染到 DOM 中。render 方法应该是纯函数,即不应该改变 state 或产生任何副作用。

import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: 'Hello, React!'
    };
  }

  render() {
    return (
      <div>
        <p>{this.state.message}</p>
      </div>
    );
  }
}

export default MyComponent;

这里 render 方法返回一个包含 <p> 标签的 div 元素,显示 state 中的 message

componentDidMount

componentDidMount 在组件被插入到 DOM 后立即调用。这是执行需要访问 DOM 元素或进行网络请求等副作用操作的最佳时机。

import React, { Component } from'react';

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

  componentDidMount() {
    fetch('https://example.com/api/data')
    .then(response => response.json())
    .then(data => this.setState({ data }));
  }

  render() {
    return (
      <div>
        {this.state.data? <p>{JSON.stringify(this.state.data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,componentDidMount 方法通过 fetch 发起网络请求,获取数据并更新 state,然后在 render 方法中根据 state 的数据状态显示相应的内容。

更新阶段(Updating)

当组件的 propsstate 发生变化时,组件会进入更新阶段。在这个阶段,React 会调用一系列的生命周期方法来处理更新。

getDerivedStateFromProps

如前文所述,getDerivedStateFromProps 会在组件更新时再次被调用,以便根据新的 props 更新 state

shouldComponentUpdate

shouldComponentUpdate 方法决定组件是否应该更新。它接收 nextPropsnextState 作为参数,并返回一个布尔值。如果返回 true,组件将继续更新;如果返回 false,组件将不会更新,后续的 render 和其他更新相关的生命周期方法将不会被调用。

import React, { Component } from'react';

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

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.count!== this.state.count;
  }

  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;

在这个例子中,shouldComponentUpdate 方法只在 state 中的 count 发生变化时返回 true,这样可以避免不必要的更新,提高组件的性能。

render

在更新阶段,render 方法同样会被调用,根据新的 propsstate 重新渲染组件。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdaterender 之后、componentDidUpdate 之前被调用。它可以用于在 DOM 更新之前捕获一些信息,比如滚动位置。该方法接收 prevPropsprevState 作为参数,并返回一个值,这个值会作为 componentDidUpdate 的第三个参数传递。

import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [1, 2, 3]
    };
    this.listRef = React.createRef();
  }

  handleClick() {
    this.setState(prevState => ({
      items: [...prevState.items, prevState.items.length + 1]
    }));
  }

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

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

  render() {
    return (
      <div>
        <ul ref={this.listRef}>
          {this.state.items.map(item => (
            <li key={item}>{item}</li>
          ))}
        </ul>
        <button onClick={() => this.handleClick()}>Add Item</button>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,getSnapshotBeforeUpdate 方法在列表长度发生变化时捕获列表的滚动高度,并在 componentDidUpdate 方法中恢复滚动位置,确保用户的滚动位置不会因为列表的更新而丢失。

componentDidUpdate

componentDidUpdate 在组件更新并重新渲染到 DOM 后被调用。它接收 prevPropsprevStatesnapshot(如果 getSnapshotBeforeUpdate 返回了值)作为参数。可以在这个方法中执行依赖于 DOM 更新的操作,比如操作更新后的 DOM,或者根据新的 propsstate 进行额外的数据请求。

import React, { Component } from'react';

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

  componentDidUpdate(prevProps, prevState) {
    if (prevState.count!== this.state.count) {
      console.log('Count has been updated:', this.state.count);
    }
  }

  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;

这里 componentDidUpdate 方法在 state 中的 count 发生变化时打印日志,展示了在组件更新后执行自定义操作的能力。

卸载阶段(Unmounting)

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

componentWillUnmount

componentWillUnmount 方法在组件即将从 DOM 中移除时被调用。可以在这个方法中执行清理操作,比如取消网络请求、清除定时器等,以避免内存泄漏。

import React, { Component } from'react';

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

  componentDidMount() {
    const interval = setInterval(() => {
      console.log('Timer is running...');
    }, 1000);
    this.setState({ timer: interval });
  }

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

  render() {
    return <div>Component with a timer</div>;
  }
}

export default MyComponent;

在上述代码中,componentDidMount 方法设置了一个定时器,每秒打印一次日志。而在 componentWillUnmount 方法中,当组件即将卸载时,我们清除了这个定时器,防止在组件卸载后定时器仍然运行,从而导致内存泄漏。

错误处理

在 React 16 及以上版本中,引入了错误边界的概念,用于捕获组件树中 JavaScript 错误,并优雅地处理这些错误,防止应用崩溃。错误边界是一个 React 组件,它可以捕获其下方子组件树中任何位置抛出的错误,并渲染备用 UI。

componentDidCatch

componentDidCatch 方法在后代组件抛出错误后被调用。它接收 error(错误对象)和 errorInfo(包含错误发生位置的信息)作为参数。

import React, { Component } from'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }

  componentDidCatch(error, errorInfo) {
    console.log('Error caught:', error, errorInfo);
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <div>An error occurred. Please try again later.</div>;
    }
    return this.props.children;
  }
}

class MyComponent extends Component {
  render() {
    throw new Error('Simulated error');
    return <div>My Component</div>;
  }
}

function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

export default App;

在这个例子中,ErrorBoundary 组件通过 componentDidCatch 捕获了 MyComponent 中抛出的错误,并在 state 中设置 hasErrortrue,从而渲染备用的错误提示 UI。这样可以保证即使子组件出现错误,整个应用不会崩溃,提高了用户体验。

总结 React 生命周期方法执行顺序

  1. 挂载阶段constructor -> getDerivedStateFromProps -> render -> componentDidMount
  2. 更新阶段getDerivedStateFromProps -> shouldComponentUpdate(若返回 true) -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
  3. 卸载阶段componentWillUnmount
  4. 错误处理componentDidCatch(在后代组件抛出错误时)

理解 React 生命周期方法的执行顺序对于编写高效、健壮的 React 应用至关重要。通过合理利用这些生命周期方法,开发者可以更好地控制组件的行为,实现复杂的业务逻辑,提高应用的性能和用户体验。在实际开发中,应根据具体需求选择合适的生命周期方法,并注意避免在错误的阶段执行不适当的操作,以确保代码的稳定性和可维护性。同时,随着 React 的不断发展,生命周期方法也可能会有一些变化,开发者需要关注官方文档,及时更新自己的知识,以适应新的特性和最佳实践。