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 = ' ...
随机推荐
- JS 深浅拷贝
首先理解概念 浅拷贝: 只复制对象的基本类型, 对象类型, 仍属于原来的引用. 深拷贝: 不紧复制对象的基本类, 同时也复制原对象中的对象.就是说完全是新对象产生的. 首先看浅拷贝 //浅拷贝 var ...
- 耿丹CS16-2班第五次作业汇总
Deadline: 2016-10-26 23:59 作业内容 实验4-1 求1到20的阶乘的和,其中求阶乘用函数完成. 实验4-2 写一个判素数的函数,在主函数输入一个整数,输出其是否是素数的信息. ...
- Eclipse使用Maven tomcat:run命令启动web项目时修改默认端口
- JAVA基础学习——1.2 环境搭建 之eclipse安装及中文化
安装好jdk,配置好环境变量以后,下面就可以进行安装eclipse了. 闲话少说,eclipse下载地址:http://www.eclipse.org/downloads/ 不大用关注checksum ...
- typeScence
- iOS10 远程推送代码 以及服务器端代码(.net)
// // AppDelegate.m // MyPushDemo // // Created by justapple on 16/12/25. // Copyright © 2016年 dengq ...
- MyEclipse 常用快捷键
MyEclipse 常用快捷键 编辑: Ctrl+1 快速修复(最经典的快捷键,就不用多说了,可以解决很多问题,比如import类.try catch包围等) Ctrl+Shift+F 格式化当前代码 ...
- 记录NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。
Nonnull区域设置(Audited Regions) 如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事.苹果为了减轻我们的工作量,专门提供了两个宏:NS_AS ...
- python 装饰器
#!/usr/bin/env python3 #-*-encoding:utf-8-*- def w3(*args, **kwargs): ') def w1(): def ww1(func): de ...
- [Android Pro] Android开发实践:为什么要继承onMeasure()
reference to : http://www.linuxidc.com/Linux/2014-12/110164.htm Android开 发中偶尔会用到自定义View,一般情况下,自定义Vie ...