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 = ' ...
随机推荐
- HTML <input type="file">上传文件——结合asp.net的一个文件上传示例
HTML的代码:(关键是要在form里设置enctype="multipart/form-data",这样才能在提交表单时,将文件以二进制流的形式传输到服务器) 一. <fo ...
- 微信共享收货地址 edit_address:fail 吐白沫级解决方案
又被微信坑了一整天,看完官方文档怎么测试都不通过,我一直怀疑是新版本微信支付我没有设置“共享收货地址”开关造成的. 后来经过验证,新版本不需要做这件事了. 那么,我没错,是微信的文档没及时更新... ...
- 数组中的每一个对象执行一次方法:makeObjectsPerformSelector
1, 为数组中的每一个button添加点击事件: [_buttonArray makeObjectsPerformSelector:@selector(addTarget:self action:( ...
- 【openStack】Libcloud 如何支持 keystone V3?
Examples This section includes some examples which show how to use the newly available functionality ...
- NIO及Reactor模式
关于Nio Java NIO即Java Non-blocking IO(Java非阻塞I/O),是Jdk1.4之后增加的一套操作I/O工具包,又被叫做Java New IO. Nio要去解决的问题 N ...
- Python-第三方库requests详解
Requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库.它比 urllib 更加方便,可以节约我们大量的工作,完全满足 HTT ...
- LeetCode 209 Minimum Size Subarray Sum
Problem: Given an array of n positive integers and a positive integer s, find the minimal length of ...
- 【转】Linux makefile 教程 非常详细,且易懂
From: http://blog.csdn.net/liang13664759/article/details/1771246 最近在学习Linux下的C编程,买了一本叫<Linux环境下的C ...
- MVC -- 后台RedirectToAction传递实体类与字符串
1.MVC -- 后台RedirectToAction传递实体类 RedirectToAction(控制器,控制器方法,实体类) 2.MVC -- 后台RedirectToAction传递字符串 Re ...
- 在js中添加新节点
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...