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

React 新旧生命周期方法的对比分析

2023-08-081.2k 阅读

React 生命周期概述

在 React 中,组件的生命周期指的是组件从被创建到被销毁的整个过程。这个过程中,React 提供了一系列的生命周期方法,让开发者能够在特定的阶段执行相应的操作。例如,在组件创建时进行数据的初始化,在组件更新时进行 DOM 的操作,以及在组件销毁时清理定时器等副作用。

React 的生命周期方法可以分为三个阶段:挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting)。每个阶段都有特定的生命周期方法可供开发者使用。

旧版 React 生命周期方法

挂载阶段

  1. constructor(props) 这是 ES6 类的构造函数。在 React 组件中,它是组件实例化后第一个被调用的方法。通常在这里进行状态(state)的初始化以及绑定事件处理函数。
class OldComponent extends React.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>
    );
  }
}
  1. componentWillMount() 这个方法在组件即将被插入到 DOM 之前调用。在这个方法中可以进行一些准备工作,比如发起网络请求。不过,由于 React 16.3 之后引入了异步渲染机制,这个方法可能会被调用多次,所以不建议在这里发起网络请求。
class OldComponent extends React.Component {
  componentWillMount() {
    console.log('Component will mount');
  }
  render() {
    return <div>Old Component</div>;
  }
}
  1. render() 这是 React 组件中唯一必需的方法。它负责返回需要渲染的 JSX 元素。在这个方法中,应该是纯函数,不应该有副作用,也不应该修改组件的状态。
class OldComponent extends React.Component {
  render() {
    return <div>Old Component</div>;
  }
}
  1. componentDidMount() 这个方法在组件被成功插入到 DOM 之后调用。在这里适合进行一些依赖于 DOM 的操作,比如初始化第三方插件,或者发起网络请求。
class OldComponent extends React.Component {
  componentDidMount() {
    console.log('Component did mount');
    // 发起网络请求示例
    fetch('https://example.com/api/data')
     .then(response => response.json())
     .then(data => console.log(data));
  }
  render() {
    return <div>Old Component</div>;
  }
}

更新阶段

  1. componentWillReceiveProps(nextProps) 这个方法在组件接收到新的 props 时被调用。通过比较 nextPropsthis.props,可以决定是否更新组件的状态。不过,从 React 16.3 开始,这个方法逐渐被废弃,推荐使用 getDerivedStateFromProps 代替。
class OldComponent extends React.Component {
  componentWillReceiveProps(nextProps) {
    if (nextProps.value!== this.props.value) {
      this.setState({
        newStateValue: nextProps.value
      });
    }
  }
  render() {
    return <div>Old Component</div>;
  }
}
  1. shouldComponentUpdate(nextProps, nextState) 这个方法用于决定组件是否需要更新。返回 true 表示组件需要更新,返回 false 则表示组件不需要更新。通过合理地实现这个方法,可以提高组件的性能,避免不必要的渲染。
class OldComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.value!== this.props.value || nextState.count!== this.state.count) {
      return true;
    }
    return false;
  }
  render() {
    return <div>Old Component</div>;
  }
}
  1. componentWillUpdate(nextProps, nextState) 这个方法在组件即将更新之前被调用。在这个方法中不能调用 setState,因为可能会导致无限循环。从 React 16.3 开始,它也逐渐被废弃,推荐使用 getSnapshotBeforeUpdate 代替。
class OldComponent extends React.Component {
  componentWillUpdate(nextProps, nextState) {
    console.log('Component will update');
  }
  render() {
    return <div>Old Component</div>;
  }
}
  1. render() 同挂载阶段的 render 方法,在更新阶段也会被调用,返回新的 JSX 元素。
  2. componentDidUpdate(prevProps, prevState) 这个方法在组件更新完成后被调用。在这里可以进行一些依赖于更新后 DOM 的操作,比如操作更新后的 DOM 元素,或者根据新的 props 或 state 进行一些副作用操作。
class OldComponent extends React.Component {
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.value!== this.props.value) {
      // 操作更新后的 DOM
      const divElement = document.getElementById('myDiv');
      divElement.textContent = this.props.value;
    }
  }
  render() {
    return <div id="myDiv">{this.props.value}</div>;
  }
}

卸载阶段

  1. componentWillUnmount() 这个方法在组件即将从 DOM 中移除时被调用。在这里适合进行一些清理工作,比如清除定时器、取消网络请求等。
class OldComponent extends React.Component {
  constructor(props) {
    super(props);
    this.timer = null;
  }
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('Timer is running');
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.timer);
    console.log('Component will unmount, timer cleared');
  }
  render() {
    return <div>Old Component</div>;
  }
}

新版 React 生命周期方法

挂载阶段

  1. constructor(props) 与旧版相同,用于初始化状态和绑定事件处理函数。
class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null
    };
    this.fetchData = this.fetchData.bind(this);
  }
  fetchData() {
    // 模拟异步数据获取
    setTimeout(() => {
      this.setState({
        data: 'Some fetched data'
      });
    }, 2000);
  }
  render() {
    return (
      <div>
        <button onClick={this.fetchData}>Fetch Data</button>
        {this.state.data && <p>{this.state.data}</p>}
      </div>
    );
  }
}
  1. static getDerivedStateFromProps(props, state) 这是一个静态方法,在组件挂载和更新时都会被调用。它的作用是根据新的 props 来更新 state。返回一个对象来更新 state,如果不需要更新则返回 null
class NewComponent extends React.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>{this.state.value}</div>;
  }
}
  1. render() 与旧版相同,负责返回 JSX 元素。
  2. componentDidMount() 与旧版相同,在组件挂载后执行,适合进行 DOM 操作和发起网络请求。
class NewComponent extends React.Component {
  componentDidMount() {
    console.log('Component did mount');
    // 发起网络请求示例
    fetch('https://example.com/api/data')
     .then(response => response.json())
     .then(data => console.log(data));
  }
  render() {
    return <div>New Component</div>;
  }
}

更新阶段

  1. static getDerivedStateFromProps(props, state) 同挂载阶段,在更新时根据新的 props 更新 state。
  2. shouldComponentUpdate(nextProps, nextState) 与旧版相同,决定组件是否需要更新。
class NewComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.value!== this.props.value || nextState.count!== this.state.count) {
      return true;
    }
    return false;
  }
  render() {
    return <div>New Component</div>;
  }
}
  1. getSnapshotBeforeUpdate(prevProps, prevState) 这个方法在 render 之后,componentDidUpdate 之前被调用。它的返回值会作为 componentDidUpdate 的第三个参数。可以用来捕获一些更新前的 DOM 状态,比如滚动位置等。
class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [1, 2, 3]
    };
  }
  addItem = () => {
    this.setState(prevState => {
      const newList = [...prevState.list, prevState.list.length + 1];
      return {
        list: newList
      };
    });
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
    const listElement = document.getElementById('list');
    return listElement.scrollHeight;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    const listElement = document.getElementById('list');
    listElement.scrollTop = listElement.scrollHeight - snapshot;
  }
  render() {
    return (
      <div>
        <button onClick={this.addItem}>Add Item</button>
        <ul id="list">
          {this.state.list.map(item => (
            <li key={item}>{item}</li>
          ))}
        </ul>
      </div>
    );
  }
}
  1. render() 同挂载阶段,返回更新后的 JSX 元素。
  2. componentDidUpdate(prevProps, prevState, snapshot) 与旧版相比,多了一个 snapshot 参数,这个参数是 getSnapshotBeforeUpdate 的返回值。
class NewComponent extends React.Component {
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('Snapshot:', snapshot);
  }
  render() {
    return <div>New Component</div>;
  }
}

卸载阶段

  1. componentWillUnmount() 与旧版相同,在组件卸载时执行清理操作。
class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.timer = null;
  }
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('Timer is running');
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.timer);
    console.log('Component will unmount, timer cleared');
  }
  render() {
    return <div>New Component</div>;
  }
}

新旧生命周期方法对比分析

  1. 废弃方法的原因
    • componentWillMount:React 16.3 引入异步渲染机制后,componentWillMount 可能会被调用多次,这与之前的同步渲染行为不同。在这个方法中发起网络请求可能会导致重复请求等问题,所以不推荐使用。
    • componentWillReceiveProps:这个方法容易导致 state 和 props 之间的不一致,并且在异步渲染环境下可能会出现问题。getDerivedStateFromProps 作为静态方法,更明确地表明它只用于根据 props 更新 state,避免了一些潜在的问题。
    • componentWillUpdate:在异步渲染下,componentWillUpdate 可能会被调用多次,并且在这个方法中不能调用 setState,使用起来比较受限。getSnapshotBeforeUpdatecomponentDidUpdate 组合可以更好地完成在更新阶段需要做的事情。
  2. 新增方法的优势
    • getDerivedStateFromProps:通过静态方法的形式,更清晰地将根据 props 更新 state 的逻辑分离出来。它在组件挂载和更新时都会被调用,使得开发者可以在这两个阶段统一处理根据 props 更新 state 的逻辑。
    • getSnapshotBeforeUpdate:这个方法可以在更新前捕获 DOM 状态,然后在 componentDidUpdate 中使用这个状态。这对于一些需要在更新后根据更新前的 DOM 状态进行操作的场景非常有用,比如保持滚动位置等。
  3. 性能和可维护性
    • 新版生命周期方法在异步渲染环境下表现更好,通过明确各个阶段的方法职责,使得代码的可维护性更高。例如,shouldComponentUpdate 依然是控制组件是否更新的关键方法,合理使用它可以提高性能。而 getDerivedStateFromPropsgetSnapshotBeforeUpdate 等方法使得代码逻辑更加清晰,减少了在不同生命周期方法中可能出现的混淆。
  4. 迁移建议
    • 如果项目中使用了旧版生命周期方法,对于 componentWillMount,可以将相关逻辑移到 constructorcomponentDidMount 中。对于 componentWillReceiveProps,可以用 getDerivedStateFromProps 替代。对于 componentWillUpdate,可以用 getSnapshotBeforeUpdatecomponentDidUpdate 替代。在迁移过程中,需要仔细检查代码逻辑,确保新的生命周期方法能够正确地实现原有的功能。

通过对 React 新旧生命周期方法的详细对比分析,开发者可以更好地理解 React 组件的生命周期管理,在开发过程中选择合适的方法,提高代码的质量和性能。无论是新的项目还是对旧项目的升级,掌握这些知识都有助于编写出更健壮和高效的 React 应用。