React 性能优化,你需要知道的几个点

2023-10-26

转自于:https://www.jianshu.com/p/333f390f2e84

写了一段时间的react之后,渐渐的喜欢上了使用react来写应用。

我们知道,Facebook在推出react时打出的旗号之一就是高性能。

今天我们还一起来聊一聊react的性能优化,思考还能通过哪些手段来提升React的性能,使我们的react更快,性能更好。

一,react组件的性能优化(渲染角度优化)

1,react性能查看工具

再讲性能优化之前,我们需要先来了解一下如何查看react加载组件时所耗费的时间的工具,在react 16版本之前我们可以使用React Perf来查看。

大家可以在chorme中先安装React Perf扩展,然后在入口文件或者reduxstore.js中加入相应的代码即可:

 

React Perf

在最新的React16版本中,我们可以直接在url后加上?react_pref,就可以在chrome浏览器的performance,我们可以查看User Timeing来查看组件的加载时间。


 

react16.0_pref

 

使用此工具的具体操作大家可以看下图:

react16.0_pref.gif


2,单个react组件性能优化

2.1,render里面尽量减少新建变量和bind函数,传递参数是尽量减少传递参数的数量。

首先我们先思考一个问题,比如我要实现一个点击按钮使相应的num增加1,我们有哪一些方法。

大家应该都能想到,无非就是三种,如下图:

react_function

第一种是在构造函数中绑定this,第二种是在render()函数里面绑定this,第三种就是使用箭头函数,都能实现上述方法;

但是哪一种方法的性能最好,是我们要考虑的问题。应该大家都知道答案:第一种的性能最好

因为第一种,构造函数每一次渲染的时候只会执行一遍;

而第二种方法,在每次render()的时候都会重新执行一遍函数;

第三种方法的话,每一次render()的时候,都会生成一个新的箭头函数,即使两个箭头函数的内容是一样的。

第三种方法我们可以举一个例子,因为react判断是否需要进行render浅层比较,简单来说就是通过===来判断的,如果state或者prop的类型是字符串或者数字,只要值相同,那么浅层比较就会认为其相同;

但是如果前者的类型是复杂的对象的时候,我们知道对象是引用类型,浅层比较只会认为这两个prop是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是两个不同的prop

举个例子:

当我们给组件Foo给名为styleprop赋值;

 

<Foo style={{ color:"red" }}

使用这种方法,每一次渲染都会被认为是一个style这个prop发生了变化,因为每一次都会产生一个对象给style

那么我们应该如何改进,如果想要让react渲染的时候认为前后对象类型prop相同,则必须要保证prop指向同一个javascript对象,如下:

 

const fooStyle = { color: "red" }; //取保这个初始化只执行一次,不要放在render中,可以放在构造函数中

<Foo style={fooStyle} />

这个问题是我们在平时的编码中可以避免的。


2.2,定制shouldComponentUpdate函数

shouldComponentUpdate是决定react组件什么时候能够不重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个true。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括render函数,重新渲染。

我们来看一下下面的一个例子

我们写两个组件,AppDemo组件,并写两个方法,一个改变App中的num的值,一个是改变title,我们在Demo的render中打印render函数。我们可以看到以下的效果:

我们可以清晰的看到虽然demo组件里的title值没有改变,但是还是render了。

为了解决这个问题,我们可以对demo组件进行如下的修改:

shouldComponentUpdate

只有当demo的title值发生改变的时候,我们才去render,我们可以看一下效果:

以上只是一个特别简单的一个对于shouldComponentUpdate的定制。

在最新的react中,react给我们提供了React.PureComponent,官方也在早期提供了名为react-addons-pure-render-mixin插件来重新实现shouldComponentUpdate生命周期方法。

PureComponent

通过上述的方法的效果也是和我们定制shouldComponentUpdate的效果是一致的。

但是我们要注意的是,这里的PureRender是浅比较的,因为深比较的场景是相当昂贵的。所以我们要注意我们在1.1中说到的一些注意点:不要直接为props设置对象或者数组不要将方法直接绑定在元素上,因为其实函数也是对象


2.3,Immutable.js

先配上一张经典的图和经典的一句话:

 

Immutable

Shared mutable state is the root of all evil(共享的可变状态是万恶之源)

-- Pete Hunt

javascript中的对象一般都是可变的,因为使用了引用赋值,新的对象简单的引用了原始对象,改变新对象将影响到原始对象。

举个例子:

foo = { a : 1 };
bar = foo;
bar.a = 2;

当我们给bar.a赋值后,会发现foo.a也变成了2,虽然我们可以通过深拷贝与浅拷贝解决这个问题,但是这样做非常的昂贵,对cpu和内存会造成浪费。

这里就需要用到Immutable,通过Immutable创建的Immutable Data一旦被创建,就不能再更改。对Immutable对象进行修改、添加或删除操作,都会返回一个新的Immutable对象。

这里我们将一下其中三个比较重要的数据结构

  • Map:键值对集合,对应ObjectEs6种也有专门的Map对象
  • List:有序可重复列表,对应于Array
  • ArraySet:有序且不可重复的列表

我们可以看两个例子:

使用Map生成一个immutable对象

 

import { Map , is } from 'immutable';

let obj = Map({
  'name': 'react study',
  'course': Map({name: 'react+redux'})
})

let obj1 = obj.set('name','darrell');

console.log(obj.get('course') === obj1.get('course')); // 返回true
console.log(obj === obj1); // 返回false

Immutable.is 比较的是两个对象的 hashCodevalueOf(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。

 

let obj = Map({name:1,title:'react'});
let obj1 = Map({name:1,title:'react'});
console.log(is(obj,obj1)); // 返回true

let obj2 = {name:1,title:'react'};
let obj3 = {name:1,title:'react'};
console.log(is(obj2,obj3)); // 返回false

Immutable优点

  • 减少内存的使用
  • 并发安全
  • 降低项目的复杂度
  • 便于比较复杂数据,定制shouldComponentUpdate方便
  • 时间旅行功能
  • 函数式编程

Immutable缺点

  • 学习成本
  • 库的大小(建议使用seamless-immutable)
  • 对现有项目入侵严重
  • 容易与原生的对象进行混淆

如果大家想深入了解,可以参考immutableIMMUTABLE 详解


2.4,多个react组件性能优化,key的优化

react组件在装载过程中,react通过在render方法在内存中产生一个树形结构,树上的节点代表一个react组件或者原生的Dom元素,这个树形结构就是我们所谓的Vitural Dom,react根据这个来渲染产生浏览器的Dom树。

react在更新阶段对比原有的Vitural Dom和新生成的Vitural Dom,找出不同之处,在根据不同来渲染Dom树。

react为了追求高性能,采用了时间复杂度为O(N)来比较两个属性结构的区别,因为要确切比较两个树形结构,需要通过O(N^3),这会降低性能。

我们举几个情况,大家就会马上理解:

  • 节点类型不同

    // A组件
    
    <div>
      <Todos />
    </div>
    
    // B组件
    <span>
      <Todos />
    </span>
    

    我们想把A组件更新成B组件,react在做比较的时候,发现最外面的根结点不一样,直接就废掉了之前的<div>节点,包括里面的子节点,这是一个巨大的浪费,但是为了避免O(N^3)的时间复杂度,只能采用这种方式

    所以在开发过程中,我们应该尽量避免上面的情况,不要将包裹节点的类型随意改变。

  • 两个节点类型一样

    这里包括两种情况,一种是节点是Dom类型,还有一种react组件。

    对于dom类型,我们举个例子:

    // A组件
    <div style={{color: 'red',fontSize:15}} className="welcome">
      Hello World!!!
    </div>
    
    // B组件
    <div style={{color: 'green',fontSize:15}} className="react">
      Good Bye!!!
    </div>
    

    上述A和B组件的区别是文字、classNamestyle中的color发生改变,因为Dom元素没变,React只会修改他变化的部分。

    针对react组件类型,渲染无非就是在走一遍组件实例的更新过程,最主要的就是定制shouldComponentUpdate,我们上面也有讲到,就不细讲了。

  • 多个子组件情况

    我们看两个例子就能明白

    例子一:

    // A
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
      <TodoItem text="Third" complete={false} />
    </ul>
    

    从A变到B,如果shouldComponentUpdate处理得当,我们只需要更新装载third的那一次就行。

    我们来看看下一个例子:

    // A
    <ul>
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem text="Zero" complete={false} />
      <TodoItem text="First" complete={false} />
      <TodoItem text="Second" complete={false} />
    </ul>
    

    这里因为react是采用O(n)的时间复杂度,所以会依次将text为First的改为Zero,text为Second改为First,在最后再加上一个组件,text为Second。现存的两个的text的属性都被改变了,所以会依次渲染。

    如果我们这里有1000个实例,那么就会发生1000次更新。

    这里我们就要用到Key

    简单来说,其实这一个Key就是react组件的身份证号。

    我们将上一个例子改成如下,就可以避免上面的问题了,react就能够知道其实B里面的第二个和第三个组件其实就是A中的第一个和第二个实例。

    // A
    <ul>
      <TodoItem key={1} text="First" complete={false} />
      <TodoItem key={2} text="Second" complete={false} />
    </ul>
    
    // B
    <ul>
      <TodoItem key={0} text="Zero" complete={false} />
      <TodoItem key={1} text="First" complete={false} />
      <TodoItem key={2} text="Second" complete={false} />
    </ul>
    

    不过现在,react也会提醒我们不要忘记使用key,如果没有加,在浏览器中会报错。

    react_key

    关于key的使用我们要注意的是,这个key值要稳定不变的,就如同身份证号之于我们是稳定不变的一样。

    一个常见的错误就是,拿数组的的下标值去当做key,这个是很危险的,代码如下,我们一定要避免。

    <ul>
      {
            todos.map((item, index) => {
                <TodoItem
                  key={index}
                  text={item.text}
                  completed={item.completed}
            })
      }
    </ul>
    


二,redux性能优化:reselect(数据获取时优化)

在前面的优化过程中,我们都是优化渲染来提高性能的,既然reactredux都是通过数据驱动的的方式驱动渲染过程,那么处理优化渲染过程,获取数据的过程也是需要考虑的一个优化点。

 

//下面是redux中简单的一个筛选功能
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

mapStateToProps函数作为redux store中获取数据的重要一环,当我们根据filtertodos来显示相应的待办事项的时候,我们都要遍历todos字段上的数组。

当数组比较大的时候,则会降低性能。

这个时候,reselect就应运而生了,它的动作原理:只要相关的状态没有改变,那么就直接使用上一次的缓存结果。

具体的用法我就不在这里过多介绍了,已经有很多的牛人写了相关的文章,我也不重复写了,大家如果想深入了解的话,可以参考reselect的giuhubRedux的中间件-Reselect


三:参考资料

此篇文章是参考了《深入React技术栈》和《深入浅出React与Redux》这两本书中关于对react性能优化的章节,再加上自己的动手实践与思考写的。

文章中不乏会有些错误的地方还请大家多多批评指正。

希望这篇文章对大家能有帮助,来自一个奔跑在前端路上的前端小白。



作者:darrell
链接:https://www.jianshu.com/p/333f390f2e84
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React 性能优化,你需要知道的几个点 的相关文章

  • Markdown 基本语法

    Markdown 基本语法 初级 一 什么是Markdown Markdown 是一种轻量级标记语言 它允许人们使用易读易写的纯文本格式编写文档 将格式元素添加到纯文本文档 Markdown 允许您使用易于阅读 易于编写的纯文本格式进行编写
  • [运营专题]零预算引爆个人和企业品牌

    文章推荐 Selenium 自动化测试从零实战 原文链接 原来这样做 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android 物体识别 原文链接 TensorFlow on An
  • File 类和 InputStream, OutputStream 的用法总结

    目录 一 File 类 1 File类属性 2 构造方法 3 普通方法 二 InputStream 1 方法 2 FileInputStream 三 OutputStream 1 方法 2 FileOutputStream 四 针对字符流对
  • 点云数据生成三维模型_Agisoftphotoscan生成三维模型步骤

    随着航空测量技术的飞速发展 利用低空无人飞机进行航空摄影获取遥感数据已成为现实 利用Agisoftphotoscan软件进行影像数据处理 生成数字地表模型 DSM 和正射影像图 DOM 产品的生产 数据生产流程 1 无人机的用途及种类的不同
  • FFmpeg编译配置命令

    configure help Usage configure options Options defaults in brackets after descriptions Help options help print this mess
  • 华为OD机试 - 城市聚集度(Java)

    题目描述 一张地图上有n个城市 城市和城市之间有且只有一条道路相连 要么直接相连 要么通过其它城市中转相连 可中转一次或多次 城市与城市之间的道路都不会成环 当切断通往某个城市 i 的所有道路后 地图上将分为多个连通的城市群 设该城市i的聚
  • 我在项目中遇到的一些经典功能bug

    1 传参类型不同 类型是数组 实际传的是字符串 导致重置搜索条件后导出失败 刷新页面 或者切换中英文也是刷新页面的效果 初始化赋值为null 可以导出成功 重置搜索条件后 导出失败 2 下划线 百分号 可能是适配符 特殊字符 空格 边界值
  • AlSD 系列智能安全配电装置是安科瑞电气有限公司专门为低压配电侧开发的一款智能安全用电产 品-安科瑞黄安南

    一 应用背景 电力作为一种清洁能源 给人们带来了舒适 便捷的电气化生活 与此同时 由于使用不当 维护 不及时等原因引发的漏电触电和电气火灾事故 也给人们的生命和财产带来了巨大的威胁和损失 为了防止低压配电系统发生漏电和电气火灾事故 传统的方
  • 网络运维词汇汇总

    本篇之所以起该名字 是因为我在一家网络公司工作所遇到的一些相关词汇 仅供参考 1 关系型数据库服务 RDS 关系型数据库服务 RelationalDatabase Service 简称RDS 是一种稳定可靠 可弹性伸缩的在线数据库服务 RD
  • Metasploitable2在VMware上的安装与初步渗透学习

    环境 靶机 Metasploitable2 IP 未知 攻击机 KALI IP 192 168 127 5 平台 VMware 16 2 4 一 Metasploitable2的简介 Metasploitable2是一个故意易受攻击的Lin
  • JDK8之Stream流

    1 集合处理数据的弊端 当我们在需要对集合中的元素进行操作的时候 除了必需的添加 删除 获取外 最典型的操作就是集合遍历 public class StreamTest01 public static void main String ar
  • 在cmd控制台运行java程序,错误: 编码GBK的不可映射字符?

    此错误是由于字符编码造成的 出现这样的错误 一般是因为代码中含有中文字符 注释中的中文字符也算 由于使用CMD运行java程序的时候 系统默认的编码格式是gbk 而包含中文字符的代码一般是UNICODE格式 所以直接运行含有中文字符的代码就
  • JBPM工作流管理例子

    工作中要用到JBPM写工作流 自习的时候找到一篇较好的文章 贴过来共享下 在某一公司中 部门员工要休假的话需要部门主管的批准 如果休假天数大于10天的话 在部门主管的同意后 还必须上级主管批准 如果是部门主管要休假只要上级主管批准即可 在休
  • For input string: “ “

    For input string 如果出现这样的异常报错 是指的数据转换时出错 比如字符串转整数 解决方法 去检查前端代码中 相应的值的value 有没有多了空格 删去即可 因为本身就是引用的一个int变量的值 加了一个空格后反而变成了字符
  • EI会议论文的检索报告怎么开?

    根据Engineering Village数据库 可以通过检索确定会议论文是否被EI收录 并可开具检索报告 EI检索覆盖了EI期刊和EI会议等资源 要开具EI会议论文检索报告 先是要进行查询 确保能查到后才可以开具 查询方式如下 直接查询
  • python自动导入包_【pycharm常用设置】自动导入包+自动生成文件头注释

    一 自动导入包设置 首先确保pycharm中设置 File Settings General Auto Import Python Show import popup 导入包是alt enter 键组合 如果弹出下拉菜单选项 说明缺少依赖
  • C语言-队列

    队列是一种特殊的线性表 特殊之处在于它只允许在表的前端 front 进行删除操作 而在表的后端 rear 进行插入操作 和栈一样 队列是一种操作受限制的线性表 进行插入操作的端称为队尾 进行删除操作的端称为队头 队列的特性 先进先出 后进后
  • Windows系统漏洞之5次Shift漏洞启动计算机

    一 原理知识 当我们使用计算机时 连续按下5次shift键会弹出一个程序 程序名称为 esthc exe 其路径为 c windows system32 sethc exe 该系统漏洞由于部分Win7及Win10在未进入系统时 可以通过连续

随机推荐

  • “基于医疗知识图谱的问答系统”代码解析(二)

    基于医疗知识图谱的问答系统 代码解析 二 question classifier py 问题分类器代码解析 基于知识医疗图谱的问答系统 代码解析 一 基于医疗知识图谱的问答系统 代码解析 三 基于医疗知识图谱的问答系统 代码解析 四 基于医
  • docker部署Portainer

    一 Portainer概述 Portainer可以在Docker上运行 而且部署起来非常简单 Portainer是Docker的图形化管理工具 提供状态显示面板 应用模板快速部署 容器镜像网络数据卷的基本操作 包括上传下载镜像 创建容器等操
  • unity 手机上获取手指触摸位置_Unity 操作检测的各种实现#2手机端 - 触屏与手势...

    写在前面 继续填坑了 这次是讲手机端 电脑端 按我跳转 键盘按键控制 上文内容 鼠标点击控制 上文内容 手机端 触屏控制 本文内容 手势控制 本文内容 重力控制 下文内容 陀螺仪控制 下文内容 本文的限制 接上文 本文主要讲在手机检查操作的
  • JavaScript 实现 -- 选择排序

    文章目录 选择排序 原理 代码实现 时间复杂度和稳定性 选择排序 选择排序是一种简单直观的排序算法 原理 第一次从待排序的数据元素中选出最小 或最大 的一个元素 存放在序列的起始位置 然后再从剩余的未排序元素中寻找到最小 大 元素 然后放到
  • python传奇自动打怪脚本_传奇自动刷怪脚本,节省服务器资源

    传奇自动刷怪脚本 节省服务器资源 人到怪到 人走怪没 节省服务器资源哦 main if checkhum d716 1 checkmonmap d716 20 act goto exit elseact goto shuaguai shua
  • C# 基本语法 char数据类型

    参见 Visual C 程序设计基础教程 P21 字符数据类型char用来处理Unicode字符 Unicode是16位字符 char变量以无符号16位字符 2字节 数字的形式存储 取值范围为0到65535 每一个数字代表一个Unicode
  • 二元回归模型matlab代码,多元线性回归Matlab代码

    多元线性回归 b 回归系数 bint 回归系数的置信度为95 的置信区间 r 残差 rint 各残差的置信区间 stats 用于检验回归模型的统计量 有四个数值 相关系数R 2 F值 与F对应的概率p 误差方差 相关系数R 2越接近1 说明
  • 标题:SDXL0.9技术详解:图像生成的新高度

    摘要 本文首先概述了图像生成模型SDXL0 9的特点 它相较早期模型显著提高了参数量级 采用云计算资源进行训练 进行知识图谱预训练以及优化了模型结构 文章然后介绍了SDXL0 9在图像质量 生成速度 语义一致性等指标上的进步 在技术原理上
  • excel快速填充并与上面的单元格内容一致(想要的结果如下图)

    1 选中单元格 2 按F5 点击 定位条件 再点击 空值 3 输入 上一个单元格 本例中就是 A1 4 Ctrl Enter 在这里插入图片描述
  • dedecms后台修改“系统基本参数”无效

    安装完dedecms的一些网站模板后 还原备份数据 系统基本参数也将会自动还原 能在 系统设置 下的 系统基本参数 里看到 但是 我遇到的情况是 无论怎么在系统基本参数里修改内容 更新系统缓存 都没能生效 后来通过直接修改代码实现 方法如下
  • 2020-03-05T06:26:07.000+0000

    JAVA JSON传输过程中时间问题 2020 03 05T06 26 07 000 0000转换成正常格式 2020 03 05 14 28 39 SimpleDateFormat sdf new SimpleDateFormat yyy
  • c语言比double精度更高的数据类型_数制与数据类型

    了解数制的分类 熟悉数据类型的分类 掌握整型数据类型的使用方法 掌握浮点型数据类型的使用方法 掌握字符型数据类型的使用方法 掌握不同数据类型之间的转化规则 掌握使用typedef定义类型的方法 二进制 八进制 十六进制 数制间的转换 二进制
  • SPI基础概念

    文章目录 目的 物理接线 极性和相位 信号时序 总结 目的 SPI Serial Perripheral Interface 是一种非常常用的全双工接口 这个接口在非常简单的机制下达到了比较高的通讯速度 比它通讯速度更高的常见的要不是并口
  • Qt 之 信号参数为QSharedPointer 智能指针类型

    文章目录 智能指针作为信号参数 再谈QSharedPointer Demo 1 Demo 2 智能指针作为信号参数 connect a module to the event bus Q DECLARE METATYPE QSharedPo
  • 【宝塔】centos7 如何简单安装nacos

    前言 宝塔如何安装 请访问 宝塔官方网址 https www bt cn new download html 进行安装 宝塔 宝塔页面 安装docker 查看示例图 登录宝塔页面后台 直接点击安装 等等就按着成功了 宝塔页面 安装nacos
  • 某个网站打不开,其他网站正常的原因及解决办法

    检测网站是否适应了cdn加速 可以在命令行中输入nslookup ip地址 nslookup http www 360doc com 如果address的值是多个 就证明使用了cdn加速 可以使用cdn检测工具检测各地使用cdn加速后网络情
  • mysql references关键字_mysql--一些关键字

    primary key 和unique key 在实际工作中 查看表设计总能看到这样的情况 一个primary 一个unique 还有一个key CREATE TABLE user id int 11 NOT NULL AUTO INCRE
  • 智能RFID电动车防盗管理系统解决方案

    一 RFID城市电动自行车技术诞生 随着城市经济的快速发展 电动自行车作为大众百姓日常出行的主要交通工具 数量急速增多 据统计 至2015年底全国电动自行车保有量已超过2亿辆 并且还以每年2000千万辆的速度增长 这方便民众出行的同时 也给
  • plsql替换

    s select from wh where dl delete from up update set aw a where 1 1 and sf select t rowid from srf select a rowid a from
  • React 性能优化,你需要知道的几个点

    转自于 https www jianshu com p 333f390f2e84 写了一段时间的react之后 渐渐的喜欢上了使用react来写应用 我们知道 Facebook在推出react时打出的旗号之一就是高性能 今天我们还一起来聊一