react组件性能优化探索实践

news2024/11/16 15:50:27

React本身就非常关注性能,其提供的虚拟DOM搭配上Diff算法,实现对DOM操作最小粒度的改变也是非常的高效。然而其组件渲染机制,也决定了在对组件进行更新时还可以进行更细致的优化。

react组件渲染

react的组件渲染分为初始化渲染和更新渲染。

在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染,如下图(绿色表示已渲染,这一层是没有问题的):

但是当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):

我们的理想状态是只调用关键路径上组件的render,如下图:

但是react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,如下图(黄色表示浪费的render和虚拟DOM对比)

那么如何避免发生这个浪费问题呢,这就要牵出我们的shouldComponentUpdate

shouldComponentUpdate

react在每个组件生命周期更新的时候都会调用一个shouldComponentUpdate(nextProps, nextState)函数。它的职责就是返回true或false,true表示需要更新,false表示不需要,默认返回为true,即便你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。

为了进一步说明问题,我们再引用一张官网的图来解释,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

  • C1根节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,需要更新。
  • C2节点,红色SCU (false),表示不需要更新,所以C4,C5均不再进行检查
  • C3节点同C1,需要更新
  • C6节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,更新DOM。
  • C7节点同C2
  • C8节点,绿色SCU (true),表示需要更新,然后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。

为了避免一定程度的浪费,react官方还在0.14版本中加入了无状态组件,如下:

// es5
function HelloMessage(props) {
  return <div>Hello {props.name}</div>;
}

复制

// es6
const HelloMessage = (props) => <div>Hello {props.name}</div>;

复制

既然明白了这关键所在,现在是时候向我们的大大小小一箩筐组件开刀了。

牛刀小试,直接把一些不需要更新的组件返回false

下面我们以音量图标为例,这是一个svg图标,不需要更新,所以直接return false

import React, {Component} from 'react';

class Mic extends Component {
    constructor(props) {
      super(props);
    }
    shouldComponentUpdate() {
        return false;
    }
    render() {
        return (
            <svg className="icon-svg icon-mic" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
                <title>mic</title>
                <path className="path1" d="M15 22c2.761 0 5-2.239 5-5v-12c0-2.761-2.239-5-5-5s-5 2.239-5 5v12c0 2.761 2.239 5 5 5zM22 14v3c0 3.866-3.134 7-7 7s-7-3.134-7-7v-3h-2v3c0 4.632 3.5 8.447 8 8.944v4.056h-4v2h10v-2h-4v-4.056c4.5-0.497 8-4.312 8-8.944v-3h-2z"></path>
            </svg>
        )
    }
}

export default Mic;

复制

登堂入室,对数据进行对比确定是否需要更新

先来个官网的例子,通过判断id是否改变来确定是否需要更新:

shouldComponentUpdate: function(nextProps, nextState) {
  return nextProps.id !== this.props.id;
}

复制

看起来也没那么玄乎,直接一个!==对比下就ok了,那是不是所有的都可以这样直接对比就可以呢? 我们先来看下js的两个数据类型(原始类型与引用类型)的各自比较

// 原始类型
var a = 'hello the';
var b = a;
b = b + 'world';
console.log(a === b);  // false

// 引用类型
var c = ['hello', 'the'];
var d = c;
d.push('world');
console.log(c === d); // true

复制

我们可以看到a和b不等,但是c和d是一样一样的,这修改了d,也直接修改了c,那还怎么对比(关于原始类型与引用类型的区别这里就不说明了)。

现在看来我们得分情况处理了,原始类型数据和引用类型数据得采用不同的办法处理。

原始类型数据

这没什么好说的,直接比对就是了。但是每个人都是想偷懒的,这要是每个组件都要这样去写下也挺麻烦的,于是react官方有了插件帮我们搞定这事:

  • PureRenderMixin(es5的插件)
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});

复制

  • Shallow Compare(es6的插件)
var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

复制

引用类型数据

既然引用类型数据一直返回true,那就得想办法处理,能不能把前后的数据变成不一样的引用呢,那样不就不相等了吗?于是就有了我们的不可变数据。

  • react官方提供了一个Immutability Helpers
var update = require('react-addons-update');

var newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});

复制

这样newData与myData就可以对比了。

其API如下:

  • 直接改变引用
const newValue = {
    ...oldValue
    // 在这里做你想要的修改
};

// 快速检查 —— 只要检查引用
newValue === oldValue; // false

// 如果你愿意也可以用 Object.assign 语法
const newValue2 = Object.assign({}, oldValue);

newValue2 === oldValue; // false

复制

然后在shouldComponentUpdate中进行比对

shouldComponentUpdate(nextProps) {
    return isObjectEqual(this.props, nextProps);
}

复制

const isObjectEqual = (obj1, obj2) => {
    if(!isObject(obj1) || !isObject(obj2)) {
        return false;
    }

    // 引用是否相同
    if(obj1 === obj2) {
        return true;
    }

    // 它们包含的键名是否一致?
    const item1Keys = Object.keys(obj1).sort();
    const item2Keys = Object.keys(obj2).sort();

    if(!isArrayEqual(item1Keys, item2Keys)) {
        return false;
    }

    // 属性所对应的每一个对象是否具有相同的引用?
    return item2Keys.every(key => {
        const value = obj1[key];
        const nextValue = obj2[key];

        if(value === nextValue) {
            return true;
        }

        // 数组例外,再检查一个层级的深度
        return Array.isArray(value) && 
            Array.isArray(nextValue) && 
            isArrayEqual(value, nextValue);
    });
};

const isArrayEqual = (array1 = [], array2 = []) => {
    if(array1 === array2) {
        return true;
    }

    // 检查一个层级深度
    return array1.length === array2.length &&
        array1.every((item, index) => item === array2[index]);
};

复制

我们目前采用的是在reducer里面更新数据使用Object.assign({}, state, {newkey: newValue}(数据管理采用redux),然后在组件里面根据某个具体的字段判断是否更新,如title或id等,而不是判断整个对象:

shouldComponentUpdate: function(nextProps, nextState){
    return nextProps.title !== this.props.title;
}

复制

  • immutable.js

(表示这个js太大了,所以我也没有具体实践过。)

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

至此,shouldComponentUpdate优化介绍完毕,我们接着进入另一个需要的优化点:列表类组件

列表类组件优化

列表类组件默认更新方式会比较复杂(因为可能会涉及到增删改,排序等复杂操作),所以需要加上一个key属性,提供一种除组件类之外的识别一个组件的方法。

如果某个组件key值发生变化,React会直接跳过DOM diff,重新渲染,从而节省计算提高性能。

key值除了告诉React什么时候抛弃diff直接重新渲染之外,更多的情况下可用于列表顺序发生改变的时候(如删除某项,插入某项,数据某个特定字段顺序或倒序显示),可以根据key值的位置直接调整DOM顺序。

如下例,根据时间排序图片(没有key值):

var items = sortBy(this.state.sortingTime, this.props.items);

return items.map(function(item) {
    return <img src={item.src} />;
})

复制

如果顺序发生改变,React会对元素进行diff操作并确定出最高效的操作是改变其中几个img元素的src属性。虽然如此,但是还是有了diff的计算时间,效率其实已经非常低了。

而如果加上key值之后

return <img src={item.src} key={item.id} />;

复制

React得出的结论就不是diff,而是直接使用insertBefore操作,而这个操作是移动DOM节点最高效的办法。

同理如果有一老师批改的作业列表,在批改完某个作业之后,该作业item应该被移除,有了key值之后,一检查key值,发现少了一个,于是直接移除该dom节点。

需要注意的是:每个key值是唯一的,在组件内部也不能通过this.props.key获取到。

现在我们知道了如何去优化react的组件,但是优化不能光靠自己的直觉,那么有没有个什么工具可以告诉我们什么时候需要优化呢?

如何使用perf分析组件性能

react官方提供一个插件React.addons.Perf可以帮助我们分析组件的性能,以确定是否需要优化。

下面简单说下如何使用:

  • 首先引入react-addons-perf
import Perf from 'react-addons-perf';

复制

  • 下面你可以通过console面板或者下载chrome 插件React Perf来调试,这里以console面板为例:

打开console面板,先输入Perf.start() 执行一些组件操作,引起数据变动,组件更新,然后输入Perf.stop()。(建议一次只执行一个操作,好进行分析)

再输入Perf.printInclusive查看所有涉及到的组件render,如下图(官方图片):

或者输入Perf.printWasted()查看下不需要的的浪费组件render,如下图(官方图片):

如果printWasted有数据,则表示可以优化,优化得好,是一个空数组,没有数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/611355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PB12.5 获取ip与计算机名

/建立全局的结构s_wsadata //结构如下: //version unsignedinteger //highversion unsignedinteger //description[257] character //systemstatus[129] character //maxso…

MySQL基础知识每日总结(5)

regexp检查总是返回0(没有匹配)或者1(匹配) 一、CASE表达式 1.两种写法 ①简单case表达式 case sex when 1 then 男when 2 then 女else 其他 end②搜索case表达式 casewhen sex 1 then 男when sex 2 then 女else 其他 end以上两种写法结果相同&#xff0c;但是简单case表达…

SpringCloudAlibaba:服务注册与发现之Nacos学习

目录 一、服务注册与发现介绍 1、常见注册中心 2、服务注册与发现的基本流程是: 3、服务注册与发现的主要好处是: 二、Alibaba Nacos 介绍 三、Nacos基本使用 1.linux安装包方式单节点安装部署 1. jdk安装配置 2. nacos安装 2.Nacos集成SpringBoot实现服务注册与发现…

跨国企业的组网需求分析

【案例背景】 经济全球化的背景下&#xff0c;跨国企业如何解决数据远距离传输的问题&#xff0c;稳定、安全、快速地搭建企业内网&#xff0c;影响着业务的正常开展。 【客户需求】 这家跨国企业在国内外均有办事处&#xff0c;其中国内的办公人员需要访问位于国外的内部服务…

交直流系统潮流计算(含5种控制模式)matlab代码

目录 1主要内容 2 部分代码 3 程序结果 4 下载链接 1主要内容 该程序参考文献《交直流系统潮流计算及相互关联特性分析》&#xff0c;采用5种交直流潮流控制方式&#xff1a;1.定电流定电压 2.定电流定熄弧角 3.定功率定电压 4.定功率定熄弧角 5.定触发角定电流。以9节点系…

LNMP服务

目录 一、安装Nginx服务 1.编译安装nginx服务 2.添加nginx系统服务 二、安装Mysql服务 1.编译安装mysql服务 2.修改mysql配置文件 3.设置路径环境变量 4.初始化数据库 5.添加mysql系统服务 6.修改mysql 的登录密码 三、安装配置 PHP 解析环境 1.安装环境依赖包 2.编…

【论文阅读】Neuralangelo:高保真神经表面重建

【论文阅读】Neuralangelo&#xff1a;高保真神经表面重建 Abstract1. Introduction2. Related work3. Approach3.1.预备工作3.2.数值梯度计算3.3.渐进细节层次3.4.优化 4. Experiments4.1. DTU Benchmark4.2. Tanks and Temples4.3.细节水平4.4.消融 5. Conclusion paper proj…

(栈和队列) 1047. 删除字符串中的所有相邻重复项 ——【Leetcode每日一题】

❓1047. 删除字符串中的所有相邻重复项 难度&#xff1a;简单 给出由小写字母组成的字符串 S&#xff0c;重复项删除操作会选择两个相邻且相同的字母&#xff0c;并删除它们。 在 S 上反复执行重复项删除操作&#xff0c;直到无法继续删除。 在完成所有重复项删除操作后返回…

小波和小波变换(应试)

零基础小白共计花费2小时38分04秒完成对小波的“平地起高楼”学习。 记录所有学习过程。 一、大致浏览PPT 这个阶段跳着看&#xff0c;太难的跳过 1.总结知识点 共四个部分 1.小波介绍 2.小波变换 小波变换的定义连续小波变换的定义离散小波变换小波重构 3.哈尔小波变换 …

华为OD机试真题 Java 实现【相对开音节】【2022Q4 100分】,附详细解题思路

一、题目描述 相对开音节构成的结构为辅音元音&#xff08;aeiou&#xff09;辅音(r除外)e&#xff0c;常见的单词有life,time,woke,coke,joke,note,nose,communicate&#xff0c;use&#xff0c;gate&#xff0c;same&#xff0c;late等。 给定一个字符串&#xff0c;以空格…

【Python接口自动化】--深入了解HTTP接口基本组成和网页构建原理

目录 引言 1、HTTP简介 2、HTTP原理和网页基础 2.1、 HTTP基本原理 2.2、 HTTP请求过程 2.3、 网页构成 引言 Python接口自动化有着广泛的应用场景&#xff0c;但是在实际使用过程中&#xff0c;可能会出现一些问题。比如&#xff0c;你不知道HTTP接口的基本构成&#xff0…

【Python编程从入门到实践第一版】P2 字符串入门

字符串入门 字符串的表示方法字符串基本函数.title().upper().lower()合并字符串删除空白 字符串&#xff0c;是一种常用的数据类别&#xff0c;而其值&#xff0c;可以顾名思义&#xff0c;是由字符组成的一串&#xff0c;故称为字符串&#xff1b; 字符串的表示方法 单引号、…

Andriod开发 SimpleAdapter BaseAdapter

1.SimpleAdapter 上一篇博客介绍的ArrayAdapter只能接受数组作为数据源&#xff0c;一般用于显示一行文字&#xff0c;更复杂的内容的显示可以用SimpleAdapter来实现。 SimpleAdapter接受List<Map<String, Object>>作为数据源&#xff0c;每个Map对应一个item&am…

为什么Pitch+Deck是创业者必备技能

投资术语简介&#xff1a;Pitch vs Deck vs BP BP以PPT形式出现的阅读式商业计划书&#xff0c;旨在无人讲解的前提下&#xff0c;通过文字和图表阐述项目商业信息。Deck单纯为营销演讲或融资推介所准备&#xff0c;以极少量文字图标和图像的介绍来辅助演讲的幻灯片。Pitch创业…

Spring Security 6.x 系列【52】扩展篇之集成第三方登录组件JustAuth

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列Spring Security 版本 6.1.0 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 1. 简介2. 入门案例3. 流程分析3.1 申请授权3.2 登录4. Spring Security 整…

MySQL 避「坑」指南 —— 你能设置出正确的主键吗?

前言 主键&#xff0c;可以唯一标识表中的某一行&#xff08;记录&#xff09;。合理地设置主键&#xff0c;可以帮助我们准确、快速地找到所需要的数据记录。但是设置出正确的主键似乎并没有那么简单&#xff0c;请思考如下几个问题&#xff1a; 表中的业务字段可以用来做主…

Java-Servlet解析

文章目录 前言Servlet定义内部解析总结servlet接口实际应用的servletGenericServlet类和HttpServlet类 HttpServlet中的设计模式首先看一下模板方法的定义逐步解析 SpringMVC应用 前言 从事Javaweb项目开发有一段时间了&#xff0c;一直不理解它是怎么一回事&#xff0c;后来查…

Fiddler抓包工具之高级工具栏中的Inspectors的使用

高级工具栏中的Inspectors的使用 Inspectors 页签允许你用多种不同格式查看每个请求和响应的内容。JPG 格式使用 ImageView 就可以看到图片&#xff0c;HTML/JS/CSS 使用 TextView 可以看到响应的内容。Raw标签可以查看原始的符合http标准的请求和响应头。Cookies标签可以看到…

【Web服务应用】搭建LNMP架构

搭建LNMP架构 一、编译安装MySQL服务二、安装Nginx服务三、安装配置PHP解析环境四、部署Discuz社区论坛Web应用五、部署博客论坛应用六fpm进程优化 一、编译安装MySQL服务 1.安装MySQL环境依赖包 yum -y install gcc gcc-c ncurses ncurses-devel bison cmake2、创建运行用户 u…

NIMA: Neural Image Assessment

摘要:基于自动学习的图像质量评估技术在评价图像采集管道、存储技术和共享媒体等方面具有广泛的应用价值&#xff0c;近年来已成为图像质量评估研究的热点。尽管这一问题具有主观性&#xff0c;但现有的大多数方法仅对AVA[1]和TID2013[2]等数据集提供的平均意见得分进行预测。我…