做者:ManfredHu
连接:http://www.manfredhu.com/2016/11/08/23-reactRenderingPrinciple
声明:版权全部,转载请保留本段信息,不然请不要转载css
React的优势有不少,如今不少应用都接入React这个框架。
在我看来,有下列优势:
- Facebook团队研发并维护——有团队维护更新且有质量保证
- 在MVVM结构下只起View的做用——简单接入,不须要花费大量人力重构代码
- 组件化形式构建Web应用——复用性强,提升开发效率
- 用Virtual DOM减小对DOM的频繁操做提升页面性能——批量操做减小重排(reflows)和重绘(repaints)次数——性能对比旧的方式有提升html
雅虎性能优化比较重要的点,老司机自行忽略。
以下图,HTML被浏览器解析为DOM树,CSS代码加载进来解析为样式结构体,二者关联组成渲染树,以后浏览器把渲染树绘制出来就是咱们看到的网页了。这里若是咱们对DOM树或者样式结构体作一些操做,如删除某个节点,样式改成隐藏(display:none)等等,会触发重排进而致使重绘。
前端
而React维护了一个Virtual DOM将短期的操做合并起来一块儿同步到DOM,因此这也是它对整个前端领域提出的最重要的改变。node
上面说了React在MVVM结构下只起View的做用,那么除了View,MVVM下还有Model,ViewModel。
而纯粹的View,会让整个逻辑耦合在一层下,数据也须要层层传递,不方便控制和复用。
react
故业内也有一堆的分层框架——如最先的flux,如今部门在用的Reflux,以及Redux。
对比Redux,Reflux更容易理解和上手——这也是现状,学习成本越低,接入现有业务就越容易。linux
reflux的架构很是简单,就是三部分git
因此Reflux只是让咱们,更好的去操做组件,经过一个Action命令,叫组件去干吗,组件本身经过写好的代码,对命令作出反应(变化为不一样的state状态)。github
如今你已经有了两个小工具了,写一个组件,经过Action调用组件就能够了。
写到这里,你应该能体会到,全部的引入就是为了让代码写起来更有效率,更易用,复用性更强。web
纯净的组件:在给定相同props和state的状况下会渲染出一样结果
其优势有这么几点:算法
React组件有两部分
第一部分是初始化的生命周期:
第二部分是被action触发,须要更新:
- shouldComponentUpdate
- componentWillUpdate
- render
- conponentDidUpdate
shouldComponentUpdate这个方法能够说是一个预留的插入接口。
在上面更新的时候,第一步就是调用的这个方法判断组件是否该被从新渲染。
shouldComponentUpdate是在React组件更新的生命周期中,用于判断组件是否须要从新渲染的一个接口,它有两个返回值:
- 返回true,则进入React的Virtual DOM比较过程
- 返回false,则跳过Virtual DOM比较与渲染等过程
如上图,这是一棵React Virtual DOM的树。
ShouldComponentUpdate
返回了true,即默认值,表明须要更新,进入Virtual DOM Diff
过程,返回false,不相同,须要更新ShouldComponentUpdate
返回了false,再也不更新,C4,C5由于被父节点在ShouldComponentUpdate
中返回了false,因此再也不更新ShouldComponentUpdate
返回了true进入Virtual DOM Diff
过程,比对结果为false,新旧不同,须要更新ShouldComponentUpdate
返回了true,进入Virtual DOM Diff
的过程,返回了false,即新旧两个节点不相同,因此这个节点须要更新ShouldComponentUpdate
返回了false,即不须要更新,节点不变ShouldComponentUpdate
返回了true,进入Virtual DOM Diff
比对过程,结果为true,新旧相等,不更新大概就是这么一个过程,在这里,Diff算法其实仍是比较复杂的,比较好的作法是咱们来写入ShouldComponentUpdate来本身控制组件的更新,而不是依赖React帮咱们作比较。
前面讲了那么多,相信懂React的都懂了,就再也不详细讲了,Diff算法有兴趣的能够本身去翻源码,网上也有一堆模拟实现的例子。
接下来介绍一个探索reflux&react渲染优化的例子。
这里试图,模拟一个比较现实的例子,抛开不少业务代码,让问题变得直接。
首先例子有三个组件,两个按钮,5个数字,还有一个重复打印文本的大组件。
Immutable.js
优化页面性能的例子源代码请点击这里
tpl.js
的JSX代码翻译为js代码,须要的能够本身修改,每次转化模板须要gulp
运行一下cd ./xxx/
(这里的xxx为上面对应的 ……./4updateDemo/ 目录)http-server -p 8888
端口能够自定义,http-server模块已在node_module
目录下,担忧版本依赖问题,已上传node_module
目录,直接打开就能够了1basicDemo目录是一个最原始的目录,这里你能够看到咱们哪里出现了问题。
cd ./example
打开这个没优化过的例子的目录
http-server -p xxxx
这里端口随意,不冲突就好
浏览器访问并打开控制台,会看到
5 tpl.js:32 createNum组件被更新了
tpl.js:10 TextComponent被更新了
2 tpl.js:57 createBtn组件被更新了
初始化createNum组件被渲染了5次,由于有5个,createBtn组件被渲染了两次,由于有点击开始和点击结束两个按钮。经过不一样的传参而改变形态。
点击开始会触发action
,让store的数据每次+1,点击结束会清除定时器
点击开始能够看到控制台的数据每次都会刷新整个界面的全部组件,特别是有一个大组件TextComponent
,是重复5000次文本的,每次从新渲染就有不少的损耗。这就是咱们要优化的地方——减小某些关键部分的从新渲染的次数,减小无用对比的消耗
这里你能够打开Chrome控制台的Timeline来看一下,点击开始,打开Timeline面板,每1S左右会有一个脚本执行的高峰期。
咱们知道特别是在移动端,CPU和内存的资源显得尤其稀缺(大概只能占用正常CPU和内存的10%,微信手Q等可能会由于友商系统对应用程序的优先级设计使这个限制略有提升——我说的就是小米哈哈哈),因此这样说来,性能这一块在移动手机web显得很是很是重要。
Perl是react-addons带来的性能分析工具,这里的perfDemo是结合Chrome插件的例子。
要向全局暴露一个window.Perl
变量,而后就能够愉快的配合Chrome插件使用了
这里的wasted time就是在作属性没变化的重复渲染的过程,能够优化。
用法与Chrome开发工具的TimeLine用法相似,点击start开始记录,后点击stop结束
一个简单的通用优化工具,经过浅对比(shallowCompare)方法对比新旧两个组件的状态,达到减小重复渲染的目的。
注意这里组件的store必须无关联,缘由是shallowCompare的时候,比较的是组件关联的store的数据,而例子里面store是一个,其余组件num的变化也会引发这里TextComponent组件的更新
这里将store与顶级组件APP关联起来,而后在子孙组件下自定采用props传递的方式处理(传递基本类型的数据),这样就可让pureRenderMixin的通用化了,惟一的缺点是,传递props要控制,只把组件须要的属性传递下去,这里会比较麻烦,可是这样又是性能较高又比较好理解的处理方式(相对其余要拷贝属性的方式)
*store下,option里面的对象,受pureRenderMixin的限制,不能够出现引用类型
PureRenderMixin实际上是封装了更底层的shallowCompare接口的
简单用法以下:
var PureRenderMixin = require('react').addons.PureRenderMixin;
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
就加了一个mixins,看起来简单优雅有木有。能够在众多组件里面copy通用啊有木有
那这里干了什么?
React.addons = {
CSSTransitionGroup: ReactCSSTransitionGroup,
LinkedStateMixin: LinkedStateMixin,
PureRenderMixin: ReactComponentWithPureRenderMixin, //看这里
var ReactComponentWithPureRenderMixin = {
//帮你写了一个shouldComponentUpdate方法
shouldComponentUpdate: function (nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
};
function shallowCompare(instance, nextProps, nextState) {
//分别比较props和state属性是否相等
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
function shallowEqual(objA, objB) {
if (objA === objB) { //store嵌套层级太深这里就会返回true,引用类型内存指向同一空间
return true;
}
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
var bHasOwnProperty = hasOwnProperty.bind(objB);
for (var i = 0; i < keysA.length; i++) {
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
因此PureRenderMixin这个插件,只能比较state和props为基本类型的部分。
若是有更加深层次的store数据嵌套,就要借助于update插件或者Immutablejs来深拷贝store的数据另存一份了。
update是addons里面的一个方法,旨在对拷贝对象复杂的过程来作一些语法上的优化,具体能够看react官方文档
//extend复制对象属性的时候
var newData = extend(myData, {
x: extend(myData.x, {
y: extend(myData.x.y, {z: 7}),
}),
a: extend(myData.a, {b: myData.a.b.concat(9)})
});
//用update的时候,提供了一些语法糖让你不用写那么多
var update = require('react-addons-update');
var newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
cd ./updateDemo
打开这个用addons.update优化过的例子的目录
http-server -p xxxx
这里端口随意,不冲突就好
这个例子与上面一个例子惟一的不一样是这里用了addons.update来进行store数据的复制,具体的能够看todoStore和tpl这两个模块的代码,其余基本无修改
这里update是参考了MongoDB’s query的部分语法,具体的能够看这里,类比数组方法,返回一个新的实例。
可是由Timeline的观察来看,复制对象属性的性能远比刷新一个大组件的性能高。
Immutable.js是Facebook为解决数据持久化而独立出来的一个库,传统的,好比咱们有
var a = {b:1};
function test(obj){
obj.b = 10;
return obj;
}
test(a); //10
函数对对象的操做,你不会知道这个函数对对象进行了什么操做。也就是说是封闭的。
而Immutable每次对对象的操做都会返回一个新对象
Immutable.js提供了7种不可变的数据类型:List Map Stack OrderedMap Set OrderedSet Record
,对Immutable对象的操做均会返回新的对象,例如:
var obj = {count: 1};
var map = Immutable.fromJS(obj);
var map2 = map.set('count', 2);
console.log(map.get('count')); // 1
console.log(map2.get('count')); // 2
引入Immutable.js,须要对现有的业务代码进行改动,一般是对tpl和store两部分进行操做,初始化数据的时候生成一个Immutable的数据类型,以后每次get,set操做都会返回一个共享的新的对象。
50ms渲染一次,重复渲染200次的截图,引入了immutable用了其set方法:
50ms渲染一次,重复渲染200次的截图,引入了immutable用了其update方法:
一个是immutable的阉割版,一个是AlloyTeam推的。
二者都是经过Object.defineProperty(IE9+)对set和get操做进行处理,优势是文件比较小。
本身设想,组件化运用到极致,应该是像微信weui那样
这里也思考一些可能作到的变化: