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

React getDerivedStateFromProps 的工作原理

2021-09-286.3k 阅读

React 中的 getDerivedStateFromProps 概述

在 React 开发中,getDerivedStateFromProps 是一个静态方法,它在组件实例化以及接收到新的 props 时被调用。它的主要作用是根据 props 的变化来更新组件的 state。该方法在 React v16.3 版本被引入,旨在提供一种更清晰、更可控的方式来处理由 props 驱动的 state 更新逻辑。

从本质上讲,getDerivedStateFromProps 打破了传统的 React 单向数据流模型中关于 propsstate 交互的一些常规。在常规单向数据流中,props 自上而下传递,state 通常由组件自身管理并通过 setState 更新。而 getDerivedStateFromProps 允许我们在 props 变化时直接更新 state,这在一些特定场景下非常有用,比如当 props 中的某些数据需要作为 state 缓存起来,或者根据 props 的变化来重置 state 等情况。

getDerivedStateFromProps 的语法

getDerivedStateFromProps 的语法非常简单,它是一个在类组件中定义的静态方法,接收两个参数:propsstate。其基本形式如下:

class MyComponent extends React.Component {
  static getDerivedStateFromProps(props, state) {
    // 逻辑处理
    return newState;
  }
}

这里的 props 是即将被应用到组件的新 propsstate 是组件当前的 state。该方法需要返回一个对象来更新 state,如果不需要更新 state,则返回 null

何时使用 getDerivedStateFromProps

  1. 缓存 propsstate
    • 有时候我们希望在组件内部缓存 props 的某些值到 state 中,以便在后续的组件生命周期中使用,而不必每次都从 props 中读取。例如,一个显示用户信息的组件,可能在初始化时从 props 中获取用户名称并缓存到 state 中。
    • 代码示例:
import React, { Component } from'react';

class UserInfo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: ''
    };
  }
  static getDerivedStateFromProps(props, state) {
    if (props.userName!== state.userName) {
      return {
        userName: props.userName
      };
    }
    return null;
  }
  render() {
    return <div>User Name: {this.state.userName}</div>;
  }
}

export default UserInfo;

在这个例子中,当 props 中的 userName 发生变化时,getDerivedStateFromProps 会更新 state 中的 userName

  1. 根据 props 重置 state
    • 比如在一个分页组件中,当 props 中的当前页码发生变化时,我们可能希望重置组件内部的一些状态,如当前选中的项或者滚动位置等。
    • 代码示例:
import React, { Component } from'react';

class Pagination extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentPage: 1,
      selectedItem: null
    };
  }
  static getDerivedStateFromProps(props, state) {
    if (props.currentPage!== state.currentPage) {
      return {
        currentPage: props.currentPage,
        selectedItem: null
      };
    }
    return null;
  }
  render() {
    return (
      <div>
        <p>Current Page: {this.state.currentPage}</p>
        <p>Selected Item: {this.state.selectedItem}</p>
      </div>
    );
  }
}

export default Pagination;

这里当 props 中的 currentPage 变化时,不仅更新了 state 中的 currentPage,还重置了 selectedItemnull

  1. 处理 props 驱动的复杂 state 变化
    • state 的更新逻辑依赖于多个 props 的变化,并且这种变化逻辑比较复杂时,getDerivedStateFromProps 可以将这些逻辑集中处理。例如,一个图表组件,其 state 中的数据展示方式可能依赖于 props 中的数据格式、颜色主题等多个属性。
    • 代码示例:
import React, { Component } from'react';

class Chart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataToShow: [],
      colorTheme: 'default'
    };
  }
  static getDerivedStateFromProps(props, state) {
    let newDataToShow = [];
    let newColorTheme = state.colorTheme;
    if (props.dataFormat === 'newFormat' && props.colorTheme === 'dark') {
      newDataToShow = props.data.map(d => d * 2);
      newColorTheme = 'dark';
    } else if (props.dataFormat === 'oldFormat' && props.colorTheme === 'light') {
      newDataToShow = props.data.map(d => d + 1);
      newColorTheme = 'light';
    }
    if (newDataToShow.length > 0 || newColorTheme!== state.colorTheme) {
      return {
        dataToShow: newDataToShow,
        colorTheme: newColorTheme
      };
    }
    return null;
  }
  render() {
    return (
      <div>
        <p>Data to Show: {this.state.dataToShow.join(', ')}</p>
        <p>Color Theme: {this.state.colorTheme}</p>
      </div>
    );
  }
}

export default Chart;

在这个例子中,getDerivedStateFromProps 根据 props 中的 dataFormatcolorTheme 来复杂地更新 state 中的 dataToShowcolorTheme

getDerivedStateFromProps 的工作流程

  1. 组件实例化阶段
    • 当组件首次被实例化时,getDerivedStateFromProps 会被调用一次。此时,props 是组件初始化时传递进来的值,state 是组件构造函数中初始化的 state。在这个阶段,getDerivedStateFromProps 可以根据初始 props 来调整初始 state。例如,上述 UserInfo 组件在实例化时,会根据 props 中的 userName 来初始化 state 中的 userName
  2. 接收到新 props
    • 当组件接收到新的 props 时,getDerivedStateFromProps 会再次被调用。此时,props 是新传递进来的 propsstate 是组件当前的 state。该方法会对比新旧 props(通常需要手动编写对比逻辑),根据对比结果来决定是否更新 state。如果返回一个非 null 的对象,React 会将这个对象合并到当前 state 中;如果返回 null,则表示不需要更新 state

getDerivedStateFromProps 与其他生命周期方法的关系

  1. constructor 的关系
    • constructor 主要用于初始化 state 和绑定事件处理函数等操作。而 getDerivedStateFromProps 可以在组件实例化时进一步根据 props 来调整 state。在 constructor 中初始化的 state 是一个基础状态,getDerivedStateFromProps 可以对其进行修正。例如,在 UserInfo 组件中,constructor 初始化了 userName 为空字符串,getDerivedStateFromProps 可以根据 props 中的实际 userName 来更新这个初始值。
  2. componentDidUpdate 的关系
    • componentDidUpdate 在组件更新后被调用,它可以访问到更新前的 propsstate。与 getDerivedStateFromProps 不同,componentDidUpdate 主要用于副作用操作,如网络请求、DOM 操作等。而 getDerivedStateFromProps 专注于根据 props 的变化来更新 state。例如,在一个根据 props 中的搜索关键词来更新列表数据的组件中,getDerivedStateFromProps 可以根据新的搜索关键词更新 state 中的列表数据,而 componentDidUpdate 可以在列表数据更新后,触发滚动到列表顶部的操作。
  3. shouldComponentUpdate 的关系
    • shouldComponentUpdate 用于决定组件是否应该更新,它接收新的 propsstate 作为参数。getDerivedStateFromProps 会在 shouldComponentUpdate 之前被调用。getDerivedStateFromProps 更新 state 后,shouldComponentUpdate 会根据新的 propsstate 来判断是否需要继续更新组件。例如,如果 getDerivedStateFromProps 更新了 state,但 shouldComponentUpdate 判断新的 propsstate 与之前的没有实质性变化,那么组件不会进行更新。

使用 getDerivedStateFromProps 的注意事项

  1. 避免不必要的更新
    • 由于 getDerivedStateFromProps 在每次接收到新 props 时都会被调用,所以编写逻辑时要谨慎,避免不必要的 state 更新。例如,在对比 props 是否变化时,要确保对比逻辑准确,否则可能导致频繁的 state 更新,影响性能。在上述 UserInfo 组件中,如果没有准确对比 props 中的 userNamestate 中的 userName,可能会导致即使 userName 没有变化,state 也会被更新。
  2. 保持方法的纯净性
    • getDerivedStateFromProps 是一个静态方法,它应该是纯净的,即不应该产生副作用。它不能直接修改 propsstate,只能返回一个对象来更新 state。例如,不能在 getDerivedStateFromProps 中发起网络请求或者直接操作 DOM,这些操作应该放在其他生命周期方法如 componentDidMountcomponentDidUpdate 中。
  3. setState 的配合使用
    • 虽然 getDerivedStateFromProps 可以根据 props 更新 state,但在大多数情况下,setState 仍然是更新 state 的主要方式。getDerivedStateFromProps 适用于由 props 驱动的 state 更新,而 setState 更适合处理用户交互等引起的 state 更新。例如,在一个表单组件中,用户输入引起的 state 更新应该使用 setState,而根据 props 中的初始值来初始化表单 state 可以使用 getDerivedStateFromProps

实际项目中的应用场景

  1. 表单组件
    • 在表单组件中,getDerivedStateFromProps 可以用于根据 props 中的初始值来初始化表单 state。例如,一个用户编辑表单,props 中可能传递了用户的现有信息,getDerivedStateFromProps 可以将这些信息设置到表单的 state 中,以便用户进行编辑。
    • 代码示例:
import React, { Component } from'react';

class UserEditForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      email: ''
    };
  }
  static getDerivedStateFromProps(props, state) {
    if (props.user) {
      return {
        name: props.user.name,
        email: props.user.email
      };
    }
    return null;
  }
  handleChange = (e) => {
    const { name, value } = e.target;
    this.setState({
      [name]: value
    });
  }
  render() {
    return (
      <form>
        <label>Name:
          <input type="text" name="name" value={this.state.name} onChange={this.handleChange} />
        </label>
        <label>Email:
          <input type="email" name="email" value={this.state.email} onChange={this.handleChange} />
        </label>
      </form>
    );
  }
}

export default UserEditForm;
  1. 多语言切换组件
    • 对于支持多语言切换的应用,组件可能需要根据 props 中的语言设置来更新 state 中的翻译文本。getDerivedStateFromProps 可以在语言 props 变化时,及时更新 state 中的文本,以便组件重新渲染显示正确的语言内容。
    • 代码示例:
import React, { Component } from'react';

class MultilingualComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: ''
    };
  }
  static getDerivedStateFromProps(props, state) {
    const translations = {
      en: 'Hello',
      zh: '你好'
    };
    if (props.language in translations && translations[props.language]!== state.text) {
      return {
        text: translations[props.language]
      };
    }
    return null;
  }
  render() {
    return <div>{this.state.text}</div>;
  }
}

export default MultilingualComponent;
  1. 动态布局组件
    • 在一些根据屏幕尺寸或者设备类型进行动态布局的组件中,props 可能传递了当前的布局信息。getDerivedStateFromProps 可以根据这些 props 更新 state 中的布局相关状态,如显示模式、元素位置等,从而实现组件的动态布局。
    • 代码示例:
import React, { Component } from'react';

class DynamicLayoutComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      layoutMode: 'default'
    };
  }
  static getDerivedStateFromProps(props, state) {
    if (props.screenSize ==='small' && state.layoutMode!== 'compact') {
      return {
        layoutMode: 'compact'
      };
    } else if (props.screenSize === 'large' && state.layoutMode!== 'expanded') {
      return {
        layoutMode: 'expanded'
      };
    }
    return null;
  }
  render() {
    return (
      <div>
        <p>Layout Mode: {this.state.layoutMode}</p>
      </div>
    );
  }
}

export default DynamicLayoutComponent;

总结 getDerivedStateFromProps 的优势与局限性

  1. 优势
    • 清晰的 props - state 更新逻辑:使基于 propsstate 更新逻辑更加集中和清晰,避免了在多个生命周期方法中分散处理相关逻辑。例如,在上述分页组件中,将根据 props 重置 state 的逻辑放在 getDerivedStateFromProps 中,使得代码结构更清晰。
    • 早期的 state 调整:在组件实例化和接收到新 props 时就可以对 state 进行调整,为后续的组件渲染和生命周期方法执行提供更合适的 state 初始值。比如在表单组件中,根据 props 初始化表单 state
  2. 局限性
    • 性能问题:由于每次接收到新 props 都会调用,若逻辑编写不当,可能导致不必要的 state 更新和组件重新渲染,影响性能。如在 UserInfo 组件中,如果不仔细对比 propsstate,可能导致频繁更新。
    • 副作用限制:作为静态方法,不能执行副作用操作,这在一些需要在 props 变化时立即执行副作用的场景下不太方便,需要结合其他生命周期方法使用。例如,在 props 变化时需要立即发起网络请求,就需要在 componentDidUpdate 中实现。

通过深入理解 getDerivedStateFromProps 的工作原理、使用场景、与其他生命周期方法的关系以及注意事项,开发者可以更有效地在 React 项目中利用这一特性,实现更健壮、高效的前端应用开发。在实际使用中,要根据具体的业务需求和组件逻辑,谨慎地运用 getDerivedStateFromProps,确保其为项目带来积极的影响。