React 组件性能优化
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-js
和lodash
只是纯净的比较数据,效率是相对比较高的,是目前比较适合使用的PureRender
方式。建议采用immutable-js
,也可以根据项目性质决定。(ps:持续更新欢迎指正)
参考文章
React 组件性能优化的更多相关文章
- React 组件性能优化探索实践
转自:http://www.tuicool.com/articles/Ar6Zruq React本身就非常关注性能,其提供的虚拟DOM搭配上Diff算法,实现对DOM操作最小粒度的改变也是非常的高效. ...
- React组件性能优化
转自:https://segmentfault.com/a/1190000006100489 React: 一个用于构建用户界面的JAVASCRIPT库. React仅仅专注于UI层:它使用虚拟DOM ...
- React组件性能优化总结
性能优化的思路 影响网页性能最大的因素是浏览器的重排(repaint)和重绘(reflow). React的Virtual DOM就是尽可能地减少浏览器的重排和重绘. 从React渲染过程来看,如何防 ...
- react组件性能优化PureComponent
首先我们使用react组件会配合connect来连接store获取state,那么只要store中的state发生改变组件就会重新渲染,所以性能不高,一般我们可以使用shouldComponentUp ...
- 如何对react进行性能优化
React本身就非常关注性能,其提供的虚拟DOM搭配上DIff算法,实现对DOM操作最小粒度的改变也是非常高效的,然而其组件的渲染机制,也决定了在对组件更新时还可以进行更细致的优化. react组件 ...
- React Native 性能优化指南【全网最全,值得收藏】
2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了.文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能 ...
- React拖拽组件Dragact V0.1.7:教你优化React组件性能与手感
仓库地址:Dragact手感丝滑的拖拽布局组件 预览地址:支持手机端噢- 上回我们说到,Dragact组件已经进行了一系列的性能优化,然而面对大量数据的时候,依旧比较吃力,让我们来看看,优化之前的Dr ...
- React组件性能调优
React是一个专注于UI层的框架,它使用虚拟DOM技术,以保证它UI的高速渲染:使用单向数据流,因此它数据绑定更加简单:那么它内部是如何保持简单高效的UI渲染呢?这种渲染机制有可能存在什么性能问题呢 ...
- React项目性能优化
1. 使用生产版本和Fragment 1. 生产版本 确保发布的代码是生产模式下(压缩)打包的代码. 一般运行npm run build命令. 直接从webpack看配置文件,需要设置mode = ' ...
随机推荐
- 虚拟机NAT网络配置
今天虚拟机NAT模式配置网络遇到一个奇葩问题.主机能ping同虚拟机时,虚拟机不能ping同主机.相反虚拟机ping通主机时,主机ping不通虚拟机. 最后花了一个小时,终于可以互通了,做一个记录: ...
- Android中矢量动画
Android中矢量动画 Android中用<path> 标签来创建SVG,就好比控制着一支画笔,从一点到一点,动一条线. <path> 标签 支持一下属性 M = (Mx, ...
- [原创]CSS3打造动态3D气球
周末在江边晨跑的时候发现很多 当时心血来潮就想,应该可以在网页中实现一下 这几天得闲就做了一下,效果如下 (尽量在最新版本的chrome或者firefox中查看) demo下载在文章最后 预览 --& ...
- [原创]webapp/css3实战,制作一个《炉石传说》宣传页
在移动网页,尤其是webapp中常需要用到大量的css3动画,来获得良好交互体验 我之前帮朋友做了一个,可惜没帮上忙现在和大家分享一下 目标是要做一个<炉石传说>游戏的介绍宣传页面,文字内 ...
- Java Native Interface 四--JNI中引用类型
本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 JNI支持将类实例和数组类型(如jobjec ...
- php模拟http请求的方法
我在这里终结了三种方法 第一种方法:fsockopen $flag = 0; $post = ''; $errno = ''; $errstr = ''; //要post的数据 $argv = arr ...
- centos 7 配置网络
文档: https://wiki.centos.org/FAQ/CentOS7
- 将jetty嵌入到应用中的简单案例
前面说过jetty最广泛的应用是可以方便的嵌入到应用程序中,而不是作为应用服务器,下面就用最简单的demo来演示一个最简单的应用 1.下载并导入依赖 首先应该建立一个普通的java项目,然后把依赖包导 ...
- 「C++11」Lambda 表达式
维基百科上面对于 lambda 的引入是如下描述的: 在标准 C++,特别是当使用 C++ 标准程序库算法函数诸如 sort 和 find.用户经常希望能够在算法函数调用的附近定义一个临时的述部函数( ...
- go:结构体的可访问性
1.要使某个符号对其他包( package)可见(即可以访问),需要将该符号定义为以大写字母开头------摘自go相关书籍2.go只限制包内外的可访问性,而不限制同包内不同文件的可访问性 本文讨论结 ...