React组件性能优化

前言

众所周知,浏览器的重绘和重排版(reflows & repaints)(DOM操作都会引起)才是导致网页性能问题的关键。而React虚拟DOM的目的就是为了减少浏览器的重绘和重排版

说到React优化问题,就必须提下虚拟DOM虚拟DOM是React核心,通过高新的比较算法,实现了对界面上真正变化的部分进行实际的DOM操作(只是说在大部分场景下这种方式更加效率,而不是一定就是最效率的)。虽然虚拟DOM很牛逼(实际开发中我们根本无需关系其是如何运行的),但是也有缺点。如当React组件如下:

<Components>
<Components-1 />
<Components-2 />
<Components-3 />
</Components>

数据变化从Components->Components-1传递下来,React不会只重渲染Components-1和其父组件,React会以变化(props和state的变化)的最上层的组件为准生成对比的虚拟DOM,就导致了组件没必要的重渲染(即组件render方法的运行)。下面的3张图是借用网上的,是对上面组件更新的图表说明。

  • 更新绿色点组件(从根组件传递下来应用在绿色组件上的数据发生改变)

  • 理想状态我们想只更新绿色点的组件

  • 实际图中的组件都会重渲染(黄色的点是不必要的渲染,优化的方向)

React开发团队也考虑到这个问题,为我们提供了一个组件函数处理数据量大的性能问题,shouldComponentUpdate,这个方法是我们的性能优化切入点。

虚拟DOM

虚拟DOM其实就是一个 JavaScript 对象。 React 使用虚拟DOM来渲染 UI,当组件状态有更改的时候,React 会自动调用组件的 render 方法重新渲染整个组件的 UI。

当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实现了一个虚拟 DOM,组件 DOM 结构就是映射到这个虚拟 DOM 上,React 在这个虚拟 DOM 上实现了一个 diff 算法,当要更新组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个虚拟 DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。

组件渲染方式

组件渲染方式有两种初始渲染更新渲染,而我们需要优化的地方就是更新渲染。

优化关键shouldComponentUpdate

组件更新生命周期中必调用shouldComponentUpdate,字面意思是组件是否应该更新。shouldComponentUpdate默认返回true,必更新。所有当我们判断出组件没必要更新是,shouldComponentUpdate可以返回false,就达到优化效果。那如何编写判断代码呢?看下以下几种方式。

官方PureRenderMixin

React 官方提供了 PureRenderMixin 插件,其使用方法如下:

官方说明

//官方例子
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
} render() {
return <div className={this.props.className}>foo</div>;
}
}

在 React 的最新版本里面,提供了 React.PureComponent 的基础类,而不需要使用这个插件。

这个插件其实就是重写了 shouldComponentUpdate 方法,但是这都是最上层对象浅显的比较,没有进行对象深度比较,场景有所限制。那就需要我们自己重写新的PureRenderMixin。

自定义PureRenderMixin

以下重写方式是采用ES6,和React高阶组件写法,使用了lodash进行深度比较。可以看我在CodePen的例子React组件优化之lodash深度对比点击预览

import _ from 'lodash';

function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
} if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
} const keysA = Object.keys(objA);
const keysB = Object.keys(objB); if (keysA.length !== keysB.length) {
return false;
} const bHasOwnProperty = hasOwnProperty.bind(objB);
for (let i = 0; i < keysA.length; i++) {
const keyA = keysA[i]; if (objA[keyA] === objB[keyA]) {
continue;
} // special diff with Array or Object
if (_.isArray(objA[keyA])) {
if (!_.isArray(objB[keyA]) || objA[keyA].length !== objB[keyA].length) {
return false;
} else if (!_.isEqual(objA[keyA], objB[keyA])) {
return false;
}
} else if (_.isPlainObject(objA[keyA])) {
if (!_.isPlainObject(objB[keyA]) || !_.isEqual(objA[keyA], objB[keyA])) {
return false;
}
} else if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
} return true;
} function shallowCompare(instance, nextProps, nextState) {
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
} function shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
/* eslint-disable no-param-reassign */
function pureRenderDecorator(component) {
//覆盖了component中的shouldComponentUpdate方法
component.prototype.shouldComponentUpdate = shouldComponentUpdate;
return component;//Decorator不用返回,直接使用高阶组件需要return
}
/*****
*使用ES6 class 语法糖如下,decorator的没试过,decorator请使用上面的,不要return
*let pureRenderDecorator = component => class {
* constructor(props) {
* super(props);
* component.prototype.shouldComponentUpdate = shouldComponentUpdate;
* }
* render(){
* var Component = component;//自定义组件使用时要大写
* return (
* <Component {...this.props}/>
* )
* }
*}
******/
export { shallowEqual };
export default pureRenderDecorator;

这种方式可以确保props和state数无变化的情况下,不重新渲染组件。但是进行了对象深度比较,是比较不划算的。这点Facebook也是有考虑的,所以就有了immutable-js

immutable-js

immutable-js这里就不详说,这里贴一下React组件优化代码,重写shouldComponentUpdate

import { is } from 'immutable'
...//省略代码
shouldComponentUpdate(nextProps = {}, nextState = {}){
const thisProps = this.props || {},
thisState = this.state || {}; if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
} for (const key in nextProps) {
if (thisProps[key] !== nextProps[key] || !is(thisProps[key], nextProps[key])) {
//console.debug(thisProps[key],nextProps[key])
return true;
}
} for (const key in nextState) {
if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}
...//省略代码

这里面的处理前提是要使用immutable-js对象,上面的代码不是100%适合所有场景(如果全部的props和states都是immutable对象,这个是没问题的),当props中有函数对象(原生的)时,这个就会失效,需要做些而外处理。

对于 Mutable 的对象(原生的js对象就是Mutable的)的低效率操作主要体现在 复制 和 比较 上,而 Immutable 对象就是解决了这两大低效的痛点。

immutable-js的比较是比lodash深度对象比较是更有效率的。

总结

immutable-js的思想其实是跟React的虚拟DOM是一致的,都是为了减少不必要的消耗,提高性能。虚拟DOM内部处理比较复杂,而且可能还会带有一些开发人员的副作用(render中运行了一些耗时的程序),算法比较完后会相对耗时。而 immutable-jslodash只是纯净的比较数据,效率是相对比较高的,是目前比较适合使用的PureRender方式。建议采用immutable-js,也可以根据项目性质决定。(ps:持续更新欢迎指正)

参考文章

React 组件性能优化的更多相关文章

  1. React 组件性能优化探索实践

    转自:http://www.tuicool.com/articles/Ar6Zruq React本身就非常关注性能,其提供的虚拟DOM搭配上Diff算法,实现对DOM操作最小粒度的改变也是非常的高效. ...

  2. React组件性能优化

    转自:https://segmentfault.com/a/1190000006100489 React: 一个用于构建用户界面的JAVASCRIPT库. React仅仅专注于UI层:它使用虚拟DOM ...

  3. React组件性能优化总结

    性能优化的思路 影响网页性能最大的因素是浏览器的重排(repaint)和重绘(reflow). React的Virtual DOM就是尽可能地减少浏览器的重排和重绘. 从React渲染过程来看,如何防 ...

  4. react组件性能优化PureComponent

    首先我们使用react组件会配合connect来连接store获取state,那么只要store中的state发生改变组件就会重新渲染,所以性能不高,一般我们可以使用shouldComponentUp ...

  5. 如何对react进行性能优化

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

  6. React Native 性能优化指南【全网最全,值得收藏】

    2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了.文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能 ...

  7. React拖拽组件Dragact V0.1.7:教你优化React组件性能与手感

    仓库地址:Dragact手感丝滑的拖拽布局组件 预览地址:支持手机端噢- 上回我们说到,Dragact组件已经进行了一系列的性能优化,然而面对大量数据的时候,依旧比较吃力,让我们来看看,优化之前的Dr ...

  8. React组件性能调优

    React是一个专注于UI层的框架,它使用虚拟DOM技术,以保证它UI的高速渲染:使用单向数据流,因此它数据绑定更加简单:那么它内部是如何保持简单高效的UI渲染呢?这种渲染机制有可能存在什么性能问题呢 ...

  9. React项目性能优化

    1. 使用生产版本和Fragment 1. 生产版本 确保发布的代码是生产模式下(压缩)打包的代码. 一般运行npm run build命令. 直接从webpack看配置文件,需要设置mode = ' ...

随机推荐

  1. json 使用 (下)

    使用JSON JSON也就是JavaScript Object Notation,是一个描述数据的轻量级语法.JSON的优雅是因为它是JavaScript语言的一个子集.接下来你将看到它为什么如此重要 ...

  2. js作用域之常见笔试题,运行结果题

    笔试题中经常有运行结果题,而大多体型都是围绕作用域展开,下面总结了几种相关的题: 外层的变量函数内部可以找到,函数内部的变量(局部变量)外层找不到. function aaa() { var a = ...

  3. libvirt 网络手册(二):桥接网络

    原文:Bridged Network 在一个桥接网络里面,宿主机和虚拟机共享物理网卡.每一个虚拟机可以直接绑定任意可用的IPV4或IPV6局域网地址,就像一个物理机一样.桥接给libvirt网络提供最 ...

  4. phpcurl类

    1.需求 了解curl的基本get和post用法 2.例子 <?php class Curl{ private $timeout=30; public function set_timeout( ...

  5. Libvlc API 简单说明 [转]

    Libvlc API 简单说明 原文来自http://www.xuebuyuan.com/1519616.html libvlc_instance_t* libvlc_new(int argc, co ...

  6. 【项目】百度搜索广告CTR预估

    -------倒叙查看本文. 6,用auc对测试的结果进行评估: auc代码如下: #!/usr/bin/env python import sys def auc(labels,predicted_ ...

  7. ListView加载性能优化---ViewHolder---分页

    ListView是Android中一个重要的组件,可以使用它加列表数据,用户可以自己定义列表数据,同时ListView的数据加载要借助Adapter,一般情况下要在Adapter类中重写getCoun ...

  8. WhatsApp的Erlang世界

    rick 的两个ppt整理 下载:2012 2013  ,使用半年erlang后,重新看这两个ppt才发现更多值的学习的地方,从ppt中整理如下: - Prefer os:timestamp to e ...

  9. @RequestMapping 用法详解之地址映射

    @RequestMapping 用法详解之地址映射 引言: 前段时间项目中用到了RESTful模式来开发程序,但是当用POST.PUT模式提交数据时,发现服务器端接受不到提交的数据(服务器端参数绑定没 ...

  10. KAOS模型

    问题描述: 我们开发了一种针对时序数据的文件格式TSFile,本身不支持sql查询.为了让公司分析人员能够用SQL进行分析,并且应用一些机器学习算法进行预测,需要设计并实现一个TSFile与Spark ...