性能优化

每当开发者选择将React用在真实项目中时都会先问一个问题:使用react是否会让项目速度更快,更灵活,更容易维护。此外每次状态数据发生改变时都会进行重新渲染界面的处理做法会不会造成性能瓶颈?而在react内部则是通过使用一些精妙的技巧来最小化每次造成ui更新的昂贵的dom操作从而保证性能的。

避免直接作用于DOM

react实现了一层虚拟dom,它用来映射浏览器的原生dom树。通过这一层虚拟的dom,可以让react避免直接操作dom,因为直接操作浏览器dom的速度要远低于操作JavaScript对象。每当组件的属性或者状态发生改变时,react会在内存中构造一个新的虚拟dom与原先老的进行对比,用来判断是否需要更新浏览器的dom树,这样就尽可能的优化了渲染dom的性能损耗。

在此之上,react提供了组件生命周期函数,shouldComponentUpdate,组件在决定重新渲染(虚拟dom比对完毕生成最终的dom后)之前会调用该函数,该函数将是否重新渲染的权限交给了开发者,该函数默认直接返回true,表示默认直接出发dom更新:

shouldComponentUpdate: function(nextProps, nextState) {
return true;
}

值得注意的是,react会非常频繁的调用该函数,所以如果你打算自己实现该函数的逻辑,请尽可能保证性能。

比方说,你有一个拥有多个帖子的聊天应用,如果此时只有一个发生了变化,如果你如下实现了shouldComponentUpdate,react会根据情况避免重新渲染那些没有发生变化的帖子:

shouldComponentUpdate: function(nextProps, nextState) {
// TODO: return whether or not current chat thread is different to former one.
// 根据实际情况判断当前帖子的状态是否和之前不同
}

总之,react尽可能的避免了昂贵的dom操作,并且允许开发者干涉该行为。

shouldComponentUpdate实战

这里举个包含子元素的组件例子,如下图:

图中每个圆点表示一个dom节点,当某个dom节点的shouldComponentUpdate返回false时(例如c2),react就无需为其更新dom,注意,react甚至根本不会去调用c4和c5节点的shouldComponentUpdate函数哦~

图中c1和c3的shouldComponentUpdate返回了true,因此react会检查检查其它们包含的直接子节点。最有趣的是c8节点,虽然调用它的shouldComponentUpdate方法返回的是true,但react检查后发现其dom结构并未发生改变,所以react最终是不会重新渲染其浏览器dom的。

上图的情况下,react最终只会重新渲染c6,原因你应该懂的。

那么我们应该如何实现shouldComponentUpdate函数呢?假设你有一个只包含字符串的组件,如下:

React.createClass({
propTypes: {
value: React.PropTypes.string.isRequired
}, render: function() {
return <div>{this.props.value}</div>;
}
});

我们可以简单的直接实现shouldComponentUpdate如下:

shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}

目前为止一切都很顺利,处理基础类型的属性和状态是很简单的,我们可以直接使用js语言提供的===比对来实现一个mix并注入到所有组件中,事实上,react自身已经提供了一个类似的:PureRenderMixin

但是如果你的组件所拥有的属性或状态不是基础类型呢,而是复合类型呢?比方说是一个js对象,{foo: 'bar'}

React.createClass({
propTypes: {
value: React.PropTypes.object.isRequired
}, render: function() {
return <div>{this.props.value.foo}</div>;
}
});

这种情况下我们刚才实现的那种shouldComponentUpdate就歇菜了:

// 假设 this.props.value 是 { foo: 'bar' }
// 假设 nextProps.value 是 { foo: 'bar' },
// 但是nextProps和this.props对应的引用不相同
this.props.value !== nextProps.value; // true

要想修复这个问题,简单粗暴的方法是我们直接比对foo的值,如下:

shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value.foo !== nextProps.value.foo;
}

我们当然可以通过深比对来确定属性或状态是否确实发生了改变,但是这种深比对是非常昂贵的,还记得我们刚出说过shouldComponentUpdate函数的调用非常频繁么?更何况我们为每个model去单独实现一个匹配的深比对逻辑,对于开发人员来说也是非常痛苦的。最重要的是,如果我们不是很小心的处理对象引用关系的话,还会带来灾难。例如下面这个组件:

React.createClass({
getInitialState: function() {
return { value: { foo: 'bar' } };
}, onClick: function() {
var value = this.state.value;
value.foo += 'bar'; // ANTI-PATTERN!
this.setState({ value: value });
}, render: function() {
return (
<div>
<InnerComponent value={this.state.value} />
<a onClick={this.onClick}>Click me</a>
</div>
);
}
});

起初,InnerComponent组件进行渲染,它得到的value属性为{foo: 'bar'}。当用户点击链接后,父组件的状态将会更新为{ value: { foo: 'barbar' } },触发了InnerComponent组件的重新渲染,因为它得到了一个新的属性:{ foo: 'barbar' }

看上去一切都挺好的,其实问题在于,父组件和子组件供用了同一个对象的引用,当用户触发click事件时,InnerComponent的prop将会发生改变,因此它的shouldComponentUpdate函数将会被调用,而此时如果按照我们目前的shouldComponentUpdate比对逻辑的话,this.props.value.foonextProps.value.foo是相等的,因为事实上,它们同时引用同一个对象哦~所以,我们将会看到,InnerComponent的ui并没有更新。哎~,不信的话,我贴出完整代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<!--引入React库-->
<script src="lib/react.min.js"></script>
<!--引入JSX转换库-->
<script src="lib/JSXTransformer.js"></script>
<!--组件样式-->
</head>
<body>
<!--定义容器-->
<div id="content"></div> <!--声明脚本类型为JSX-->
<script type="text/jsx"> var InnerComponent = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value.foo !== nextProps.value.foo;
},
render: function() {
return (
<div>
{this.props.value.foo}
</div>
);
}
}); var OutComponent = React.createClass({
getInitialState: function() {
return { value: { foo: 'bar' } };
}, onClick: function() {
var value = this.state.value;
value.foo += 'bar'; // ANTI-PATTERN!
this.setState({ value: value });
}, render: function() {
return (
<div>
<InnerComponent value={this.state.value} />
<a onClick={this.onClick}>Click me</a>
</div>
);
}
}); React.render(<OutComponent />, document.querySelector("#content")); </script>
</body>
</html>

正式学习 React(三)番外篇 reactjs性能优化之shouldComponentUpdate的更多相关文章

  1. Netty学习笔记(番外篇) - ChannelHandler、ChannelPipeline和ChannelHandlerContext的联系

    这一篇是 ChannelHandler 和 ChannelPipeline 的番外篇,主要从源码的角度来学习 ChannelHandler.ChannelHandler 和 ChannelPipeli ...

  2. 正式学习 react(三)

    有了基础的webpack基础,我们要对react的基本语法进行学习. 我这个教程全部用es6 实现.可能会忽略一些最基本的语法讲解,这些你在官网上或者其他别的地方都比我讲的全. 今天我要讲一下reac ...

  3. 正式学习React( 三)

    最基本的jsx语法什么的,我就不介绍了,唯一觉得有用点的,就是声明周期了. 下面的内容是转来的,自己也可以网上去搜,我觉得别人归纳的挺不错的,不过写法可能不是es6的,不影响学习. 在组件的整个生命周 ...

  4. 正式学习React(五) react-redux源码分析

    磨刀不误砍柴工,咱先把react-redux里的工具函数分析一下: 源码点这里  shallowEqual.js export default function shallowEqual(objA, ...

  5. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...

  6. Opengl_入门学习分享和记录_番外篇01(MacOS上如何在Xcode 开始编辑OpenGL)

    写在前面的废话: 哈哈 ,我可真是勤勉呢,今天又来更新了,这篇文章需要大家接着昨天的番外篇00一起食用! 正文开始: 话不多说,先看代码. 这里主要全是使用的glfwwindowhint 这个函数,他 ...

  7. openresty 学习笔记番外篇:python的一些扩展库

    openresty 学习笔记番外篇:python的一些扩展库 要写一个可以使用的python程序还需要比如日志输出,读取配置文件,作为守护进程运行等 读取配置文件 使用自带的ConfigParser模 ...

  8. openresty 学习笔记番外篇:python访问RabbitMQ消息队列

    openresty 学习笔记番外篇:python访问RabbitMQ消息队列 python使用pika扩展库操作RabbitMQ的流程梳理. 客户端连接到消息队列服务器,打开一个channel. 客户 ...

  9. 《手把手教你》系列技巧篇(三十一)-java+ selenium自动化测试- Actions的相关操作-番外篇(详解教程)

    1.简介 上一篇中,宏哥说的宏哥在最后提到网站的反爬虫机制,那么宏哥在自己本地做一个网页,没有那个反爬虫的机制,谷歌浏览器是不是就可以验证成功了,宏哥就想验证一下自己想法,于是写了这一篇文章,另外也是 ...

随机推荐

  1. Can't create/write to file '/tmp/#sql_3105_0.MYI' (Errcode: 13)

    最近的项目中由于临时存储空间太大了.索性把tmp目录删除了.结果访问出现 Can't create/write to file '/tmp/#sql_3105_0.MYI' (Errcode: 13) ...

  2. centos6.4x64安装vncserver

    参考文章:http://blog.csdn.net/mchdba/article/details/43058849 主要是远程安装oracle11g需要用到这个东西: 进入系统依次执行下列命令: #查 ...

  3. ajax请求解析springmvc返回的json数据

    需要使用的框架 spring3.0 jquery1.9.0(简化ajax开发的js库) Jackson(json处理器):jackson-core-asl-1.9.2.jar,jackson-mapp ...

  4. 百度定位SDK:弥补Android基站WIFI定位缺失

    http://tech.qq.com/a/20120524/000347.htm 如今,基于位置信息的移动应用越来越多,从餐饮.购物等本地生活服务,到定向广告的匹配.移动社交网络的构建,LBS类应用的 ...

  5. 2014.8.3情人节欢乐赛【Benny的农场】

    Benny的农场 (farm.pas/.c/.cpp) 时间限制:1s.空间限制:128MB 题目描述: Benny有一片农田需要灌溉.农田的形状为矩形,并被分为许多小块.每一块中都有一些水管.共有1 ...

  6. Impala 3、Impala、Hbase整合

    Impala可以通过Hive外部表方式和HBase进行整合,步骤如下: • 步骤1:创建hbase 表,向表中添加数据 create 'test_info', 'info' put 'test_inf ...

  7. jquery第一期:运行第一个jquery

    首先下载js文件,网址jquery.com去下载,可以下载1.10版的 首先打开editplus进行编辑,添加js文件: 编写代码: <!DOCTYPE html PUBLIC "-/ ...

  8. COJ 1059 - Numeric Parity 位操作

    非常好玩的一道题.能够熟悉下位操作实现和玩一玩bitset这个容器 Description We define the parity of an integer N as the sum of the ...

  9. Lua多重继承

    http://blog.csdn.net/ssihc0/article/details/7742421 代码收藏了,以后用的到 --多重继承 local function search(k,plist ...

  10. MFC学习之程序执行过程梳理

    *首先利用全局变量对象theApp启动应用程序.这是由于这个全局对象,基类CWinApp中this的指针才干指向这个对象.假设没有这个全局对象,程序在编译时不会出错,但在执行时就会出错. *调用全局应 ...