关于 Derived State

简介: 此文章参考官方文档

关于 Derived State

组件的状态state依赖于props,且当props更新时,需要更新组件的状态state,把这种state称为derived state(派生状态)。

componentWillReceiveProps(nextProps)

componentWillReceiveProps会在已挂载的组件接收新的 props 之前被调用。如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.props 和 nextProps 并在此方法中使用 this.setState() 执行 state 转换。

举例

handleChange = (e) => {
    const { value }  = e.target;
    const { fetchData } = this.props;
    
    fetchList(value); // Async function which Will update state
}

componentWillReceiveProps(nextProps) {
    if (this.props.list !== nextProps.list) {
        const { filter } = this.state; // 用户输入
    
        this.setState({
            filteredList: heavyCompute(nextProps.list, filter),
        });
    }
}

render() {
    const { filteredList } = this.state;
    
    // 渲染数组
}

不推荐原因

These lifecycle methods have often been misunderstood and subtly misused; furthermore, we anticipate that their potential misuse may be more problematic with async rendering. Because of this, we will be adding an “UNSAFE_” prefix to these lifecycles in an upcoming release. (Here, “unsafe” refers not to security but instead conveys that code using these lifecycles will be more likely to have bugs in future versions of React , especially once async rendering is enabled.)

版本支持

  • 16.3: 首次开始支持UNSAFE_componentWillReceiveProps。
  • 16.3+: 在dev模式下,componentWillReceiveProps发出弃用警告。
  • 17.0: 不支持componentWillReceiveProps,只支持UNSAFE_componentWillReceiveProps。

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回 null 则不更新任何内容。

举例

class Demo extends Component {
    constructor(props) {
        super(props);
        
        this.state =  {
            isScrollingDown: false,
            lastRow: props.currentRow,
        };

    }
  
  static getDerivedStateFromProps(props, state) {
      if (props.currentRow !== state.lastRow) {
      return {
        isScrollingDown: props.currentRow > state.lastRow,
        lastRow: props.currentRow,
      };
    }

    // Return null to indicate no change to state.
    return null;
  }
}

注意

  • 无法与prevProps做比较,若想支持, 开发人员要自己维护prevProps状态变量。
  • getDerivedStateFromProps是静态方法,因此无法获取this,若需要使用组件实例this,转用其他生命周期函数,比如componentDidUpdate。
  • getDerivedStateFromProps不能与componentWillReceiveProps同时出现, 会把componentWillReceiveProps忽略掉。

版本支持

  • 16.3: 开始支持getDerivedStateFromProps,但这个版本有个bug,不建议使用。
  • 16.4: 修复了16.3里的bug。

反模式设计 Anti-pattern

无条件地将prop复制给state

当我们以为每当props得到更新时,会触发componentWillReceiveProps或getDerivedStateFromProps,但实际情况是父组件重新渲染时,不管props有没有更改,都会触发子组件的componentWillReceiveProps或getDerivedStateFromProps,因此可能会造成多次无用的重复计算。

class EmailInput extends Component {
  state = { email: this.props.email };
  
  componentWillReceiveProps(nextProps) {
    this.setState({ email: nextProps.email });
  }

  handleChange = event => {
    this.setState({ email: event.target.value });
  }
  
   render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

这个EmailInput组件即可以输入email,也可以通过props更新email。通过props设置email值,为了当我们从后端查询存储过的数据后,来更新state.email。但是这里有个问题,通过输入email,即通过this.setState设置email后,当EmailInput的父组件重新渲染时,会清除this.state.email。为了解决这个问题,可以通过shouldComponentUpdate来判定是否更新,但这会使组件变的复杂,而且每当props的变量数增加时,shouldComponentUpdate的复杂度也会增加。

只有当props得到更新时,更新state

class EmailInput extends Component {
  state = {
    email: this.props.email
  };

  componentWillReceiveProps(nextProps) {
    // Any time props.email changes, update state.
    if (nextProps.email !== this.props.email) {
      this.setState({
        email: nextProps.email
      });
    }
  }
  
  // ...
}

加入判断条件后,当父组件重新渲染时,可以避免不必要的更新。但其实这也会导致一个问题。 若有个InputEmail组件,且通过导航切换来显示两个email。当两个email(123@abc.com)一样,对第一个email进行更新为321@abc.com,切换到第二个email时,会保留之前的更新,即321@abc.com,而不是123@abc.com。看这个demo

解决方案

因此设计组件时,应尽量避免使用Derived State,避免通过componentWillReceiveProps或getDerivedStateFromProps来更新state,并尽量保证数据的唯一来源性(source of truth)。

Side Effect

在componentDidUpdate执行副作用函数。

受控组件 controlled component

即保持数据是受父组件控制,保持数据来源的唯一性。

const EmailInput = (props) => {
  return <input onChange={props.onChange} value={props.email} />;
}

有key的非受控组件

当props更新时,需要初始化state时,利用key值。当组件的key发生变化时,react会创建一个新的组件。

<EmailInput key={this.state.user.id} />

记忆化 Memoization

定义

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

我们使用derived state的最主要原因是为了把计算后的结果作为状态暂存在组件state里,当props发生变化时,重新计算并暂存。若计算量不大,我们可以选择不暂存结果,这样的优点是代码逻辑更加简单、清晰,省掉了维护状态state的麻烦。但计算量比较大,影响到使用体验时,除了暂存到组件state,有没有其他解决方法?

通过记忆化技术,我们可以暂存计算后的结果(对于组件是透明的),当只有函数的参数改变时,再执行计算。

举例

import memoize from "memoize-one";

class Example extends Component {
  // State only needs to hold the current filter text value:
  state = { filterText: "" };

  // Re-run the filter whenever the list array or filter text changes:
  filter = memoize(
    (list, filterText) => list.filter(item => item.text.includes(filterText))
  );

  handleChange = event => {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // Calculate the latest filtered list. If these arguments haven't changed
    // since the last render, `memoize-one` will reuse the last return value.
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>
    );
  }
}

注意

  • 一般情况下,把记忆化的函数附加的组件实例,以免有多个组件实例的情况下,记忆化的函数相互影响。
  • 注意缓存的大小限制,可能导致内存泄漏。
  • 每当父组件渲染组件时,若重新创建新的props.list,则组件中记忆化的函数会无法正常工作,即每次渲染时都要重新计算。

总结

尽量少用Derived State, 保持组件数据的来源唯一性,通过转换成受控组件,或使用componentDidUpdate, 或利用memoization技术来代替componentWillReceiveProps或getDerivedStateFromProps,则可以降低组件的复杂度,带来更高的可维护性和可扩展性。

参考链接

You Probably Don't Need Derived State
Discussion: componentWillReceiveProps vs getDerivedStateFromProps #721
The minor release of 16.4 causes BREAKING changes in getDerivedStateFromProps #12898
Memoization Wiki
memoize-one

目录
相关文章
|
机器学习/深度学习 人工智能 自然语言处理
命名实体识别的一点经验与技巧(上)
命名实体识别的一点经验与技巧(上)
622 0
|
Cloud Native Go 项目管理
敏捷项目管理解锁:2023年使用YouTrack的全面指南
敏捷项目管理解锁:2023年使用YouTrack的全面指南
3993 0
GPIO简介
本文所述IO(Input/Output PAD)主要是指集成在CMOS芯片中的连接模块,其负责芯片引脚的外部信号与芯片内部的数字/模拟模块的交互,它是一颗完整芯片设计中不可或缺的组成部分。 文章首先根据IO的使用类型,介绍了IO的分类;紧接着重点介绍了GPIO的输入/输出功能和模式...
GPIO简介
|
域名解析 SQL 网络协议
Hexo 个人博客快速部署到Gitee&Coding详细教程
Hexo 个人博客快速部署到Gitee&Coding详细教程
1577 0
Hexo 个人博客快速部署到Gitee&Coding详细教程
|
人工智能 弹性计算 前端开发
AI开发:大学生创业公司官网
假设你和几个同学做了一家创业公司,业务是AI智能体开发,你们需要快速开发一个公司官网。使用bolt.diy+通义灵码,全程零手写代码完成网站开发。部署到云端,让客户能访问。展示一个网站从功能设计,到代码开发,到云端部署的全过程。
|
存储 缓存 固态存储
阿里云服务器租用价格参考,云服务器收费标准与活动价格表参考
本文为大家展示阿里云服务器最新的收费标准与活动价格情况,以供了解和参考。
阿里云服务器租用价格参考,云服务器收费标准与活动价格表参考
|
人工智能 小程序 Android开发
鸿蒙应用开发从入门到入行 - 篇1:HarmonyOS介绍——带你深入理解鸿蒙特性
本文介绍了华为的HarmonyOS(鸿蒙系统),这是一个面向全场景的分布式操作系统,不仅适用于手机和平板,还支持电脑、车机、手表、电视等多种设备。文章详细解析了鸿蒙系统的三大特性:一次开发多端部署、可分可合自由流转、统一生态原生智能,并分析了鸿蒙系统为何能蚕食安卓市场份额的原因。猫林老师认为,鸿蒙凭借其先进的技术和国内政策支持,有望在未来的市场中占据重要地位。最后,文章提供了学习鸿蒙系统的建议和一些课后练习,帮助读者更好地理解和掌握这一系统。
1969 7
鸿蒙应用开发从入门到入行 - 篇1:HarmonyOS介绍——带你深入理解鸿蒙特性
|
数据可视化 搜索推荐 数据管理
数字化教育系统管理平台:为教务部门量身定制的可视化大屏
在教育行业数字化转型背景下,教务部门面临管理效率低下、数据分散、决策支持不足等挑战。为此,我们推出数字化教育系统可视化大屏,整合招生和学务数据,提供直观的实时分析与可视化展示,助力高效管理和科学决策。平台支持红色(招生)和绿色(学务)主题,涵盖高校数据管理、信息一览、定制化地图展示等应用场景,显著提升数据处理效率和用户体验。
|
算法 搜索推荐 数据库
二分搜索:高效的查找算法
【10月更文挑战第29天】通过对二分搜索的深入研究和应用,我们可以不断挖掘其潜力,为各种复杂问题提供高效的解决方案。相信在未来的科技发展中,二分搜索将继续发挥着重要的作用,为我们的生活和工作带来更多的便利和创新。
290 1
|
存储 JavaScript 前端开发
【面试官系列】React 中,如何在页面刷新之后保持状态?看看你知道几种~
【面试官系列】React 中,如何在页面刷新之后保持状态?看看你知道几种~
【面试官系列】React 中,如何在页面刷新之后保持状态?看看你知道几种~