文档章节

react之性能调优

b
 boySpray
发布于 2017/09/08 16:16
字数 2541
阅读 13
收藏 0

叙述

        此篇属个人的 react 开发总结。关于性能优化这一块,并没有涉及到 react 底层渲染机制,只是一些开发过程中遇到的性能问题,遵循:尽可能的减少 render 准则

        本来是想介绍一下自己开发过程遇到的性能问题的,写着写着,发现要让人明白,还是需要一大坨解释的,综合考虑读者的基础知识。

        react 是什么? render 是用来干嘛的? 有疑问的,现在就可以 return 了。议你先去看一下阮一峰大神的 react 入门基础http://www.ruanyifeng.com/blog/2015/03/react.html

react 控制 render 

        想要达到的效果          

        即避免模块进行不必要的重复渲染。开发过程中,一定要谨记这点。从小组件做起,合理的组件设计规范、页面设计逻辑,从规范上规避掉 react 性能问题!

        react 虚拟DOM

        在传统的 Web 应用中,我们往往会把数据的变化实时地更新到用户界面中,于是每次数据的微小变动都会引起 DOM 树的重新渲染。如果当前 DOM 结构较为复杂,频繁的操作很可能会引发性能问题。React 为了解决这个问题,引入了虚拟 DOM 技术。

        插播浏览器解析DOM的简略版小广告:

        在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。

        对这块有兴趣的,可以去看看这边文章:http://www.jianshu.com/p/e141d1543143

        react 内部有一套自己的性能优化的方法,即 Diff 算法。Diff 算法的最终结果,是生成一个新的虚拟DOM树,通过虚拟DOM,映射页面节点渲染。    

        我也不理解其具体的算法,大致认为,在控制 react render 的过程,就是在减少 diff 运算。我总是认为,既然都不需要重新渲染,那就不需要去进行 diff 运算,多余的运算就是浪费性能。

        我会在 shouldComponentUpdate 这个生命周期,严格控制组件的渲染,当 return false 的时候,相应的 diff 运算也会减少一些。

        对 react 生命周期不了解的,建议先去看这边文章,讲的很详细,说的是 React Native的生命周期,其实就跟 react 一样。https://race604.com/react-native-component-lifecycle/

        可以用下张图还解释这个效果

        

        图中,真正重新渲染的就只有那黄色的点,绿色的点,均是 react diff 算法直接借鉴原本的DOM树的数据,拿过来组成新的DOM树。        

        关于这点,我就不再多叙述了,只做简单描述,毕竟每个开发者遇到的问题都不一样,想要解决的问题也不尽相似。

 

react 优化性能

        前文说过,react 性能优化的准则:尽可能的减少 render 

        会触发 render 的几种情况

  • 组件的props变化
  • 页面调用 setState() 函数
  • 父组件 render,会导致子组件触发 render

        目前没在想到其他会触发 render 的情况了。减少 render 则需要往这几个方面思考。

        1、设计思想

        在做页面设构思的时候,就把性能问题给规避掉!

        react -- 组件化开发思想,把页面区分划分成很多小的模块,最终嵌套组装成页面。对于 react 开发工程师,面向对象、页面组件化全局思考,非常重要。这边,我讲讲我开发页面,是怎么样思考的。

  1.  拿到页面需求,第一时间总览所有页面需求,期间,我会记录一下所有页面看起来长的一样的模块。
  2.  设计父组件的时候,主要考虑:父组件 render,会导致子组件也触发 render。 尽量减少父组件的 render ,或者父组件的 render 会造成什么样的影响。
  3.  设计子组件的时候,主要考虑:组件的props变化而导致的页面render。可以通过shouldComponentUpdate( currentProps、nextProps )比对 currentProps、nextProps 是否有变化,进而控制组件是否要 render。

        2、React 自带 PureRender

        react创建类有几种方法,我就只说说目前用的比较多的两种。

        使用es6 创建组件类。react 里面有一个 PureComponent 组件,供开发这继承、编写react类。和 Compoennt 想比,PureComponent 中会对 shouldComponentUpdate( currentProps、nextProps )进行了一次封装,会浅层次的比对 currentProps、nextProps 是否有变化,进而控制组件是否要 render。这种比对,是浅层次的比对!!!。

        react 在以前的版本,支持使用 createReactClass 来新建组件类,不建议去使用。react在最近版本的更新也打算放弃这种法子了。这种类实现,可以引用react官方体统的插件 'react-addons-pure-render-mixin' ,配合使用 类内部的 mixins 方法,实现页面传入数据的比较。这种比对,是浅层次的比对!!!。

        浅层次比对,即只对数据进行简单的数据对比。一般,我是说一般,至少我在设计组件传入的数据,都是比较大的数据快。这种浅层次的比对,对我来说完全是不够用的。因为不够用,有时候它render,有时候又不render,变得不可控。

        3、书写规范,规避渲染

        react 也有一些书写技巧。对react不熟悉的童鞋,很容易写出个坑来。

首先,强调:

  • 父组件 render,会导致子组件触发 render
  • react 内部 diff 算法也还是能解决一些重复渲染的问题,比如 PureRender 之类的效果

常见的书写问题:

  • 调用组件往里面进行传参的时候,传入一个未知的、可变的东西。比如,传入一个匿名函数,对于匿名函数,每次进行重新进行运算的时候,都会重新生成一个函数。你把函数传入组件,组件内部比对数据的时候,会认为这是两个不同的函数(实际上真的是两个不同函数),进而有重新render。
  • render 方法是用来实现页面的,记住,render 可能会用来重复、很反复的调用!在实现页面的时候,一些能放在 render 方法之外运算的,一定要放在render之外!很多人性能问题,就是出现在 render 之内进行大量 js 运算。
  • 一些需要显示的东西,尽量算好再放入 render 之内,可以避免相同的东西又重复渲染。
比如说要实现一个 tab 切换功能

//  静态页面的写法
render(){
    return(
        <Tabs type="card" defaultActiveKey="1">
            <TabPane tab="tab1" key="1"> <Component1 /> </TabPane>
            <TabPane tab="tab2" key="2"> <Component2 /> </TabPane>
        </Tabs>
    )
}


// 现在我想动态实现这个 tab切换,即这tab是可增加的
// 我将 tab 数据放于一数组,根据 数组资源 遍历出页面
const arr = [ {title:'tab1', component: Component1}, {title:'tab2', component: Component2} ]
render(){
        <Tabs type="card" defaultActiveKey="1">
            {
                arr && arr.length && arr.map((item,index)=> {
                    const Component = item.component;
                    const title = item.title;
                    return <TabPane tab={title} key={index}> <Component /> </TabPane>
                })
            }
        </Tabs>
    )
}
// 我要增加 tab 只需要在 arr 中添加资源就行了。

        这种写法,其实是有问题的,每次重新 render 时,都会重新刷新任何子tab。这是非常大的性能消耗。

        tab展示内容,根据每次render,都是全部重新遍历出来的,对于js内存来说(至少内存指针已不一样),这是一些不同的数据,即使它们所展示的效果是一样的。

        对于这种页面实现,最好的做法,是在render之前把所要展示的内容计算好。如下:

tabs = [ // tab 要展示的页面
    <TabPane tab='tab1' key="1"> <Component1 /> </TabPane>, 
    <TabPane tab='tab2' key="2"> <Component2 /> </TabPane>
];

render(){
    return(
        <Tabs type="card" defaultActiveKey="1">
            { this.tabs }
        </Tabs>
    )
}

onAddTab(arr){  // 若要新增一个tab,只需要调用这个函数就行了。
    arr && arr.length && arr.map((item,index)=> {
        const Component = item.component;
        const title = item.title;
        this.tabs.push( <TabPane tab={title} key={index}> <Component /> </TabPane> );
    })
}
// 这么写有一个好处,你新增 tab 不会触发 tab 的重新渲染。

        对于 js 内存来说, tabs 里面的数据多了一个,但原来的数据没有变化,原数据存放的内存指针地址无变动。

        react 内部的 diff 算法能识别出显示内容无变动,即不在触发它的 render。      

        4、使用 Immutable 极致控制页面渲染

        前文说了,react 内部的 diff 算法,亦或PureComponent,对性能方面的优化只是对数据进行浅层次的比较,对于 react 新手来说,很容易造成混乱,即 render 变得不可控制。

        首先,大家要认识到以下几点:

  • 对于js来说,要对数据进行深层次的复制、比对是比较困难的
  • Javascript中对象都是引用类型,也就是a={a:1}; b=a; b.a=10;你发现a.a也变成10了。
  • 对数据的复制、比对都是浅层次的,比较数据是否相等,直接比对其指针位置,指针位置一样则数据相同。
  • js 对数据的深层次比较,很耗性能

        那如何解决以上的问题? 建议有需要的童鞋,可以去学学 Immutable 的用法,这里只做简易的介绍:

Immutable 简易介绍:

        Immutable 可以很好的解决 js 引用赋值问题、数据的深层次拷贝、深层次对比等。至少我用到这几样了。

        Immutable Data 顾名思义:不可变数据,就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

        为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。这一点跟 react 的控制组件 render 期望见到的效果很是相似。

        

© 著作权归作者所有

共有 人打赏支持
b
粉丝 0
博文 5
码字总数 4483
作品 0
厦门
高级程序员
高级前端开发,在北京这个工资算高么?

精通React、Redux、Sea.js、Require.js、Jquery、Zepto、Bootstrap等主流前端技术和框架; 熟悉前端性能分析与调优技术,参与过中大型WEB项目的开发; 了解响应式设计,能解决跨浏览器、And...

塞纳河畔左岸的咖啡
2017/07/25
221
2
iOS开发者React Native学习路线

http://blog.talisk.cn/blog/2016/08/13/RN-Learning-path-for-iOS-developer/ 既然是写给iOS开发者的,那么我默认你已经掌握iOS原生应用开发的基本知识,所以对iOS原生开发的相关内容不做解...

卡奇匠
2016/12/13
18
0
已有Android工程 集成React Native 的那些事

2017年2月27日,天气晴,我永远记得这天,我心潮澎湃,因为终于把优谈TOP 集成了React Native,从去年开始,公司陆陆续续的集成和学习React Native,通过demo的形式,写了不少组件和API,也能...

quanke
2017/02/28
0
0
iOS开发者的福音,史上最全React Native学习路线

我是一名iOS开发者,由于工作需要,接触React Native到现在也有一年多了,我发现网络上知识资源非常的多,但是能让人豁然开朗、迅速学习的还是少数,我整理出的这些文章对于初学者来说是比较...

iOS小迷糊
01/05
0
0
ELSE 技术周刊(2017.12.11期)

业界动态 Angular 5.1 & More Now Available Angular发布5.1版本,同时发布了Angular CLI 1.6版本以及首个稳定版本的Angular Material。CLI支持了Service Worker,以及带来对AppShell更好的支...

风清洋ELSE
2017/12/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

python装饰器执行顺序

上来先看代码: import timedef deco(func): def wrapper(): startTime = time.time() print "start" func() print "end" endTime =......

fang_faye
24分钟前
1
0
java常用设计模式

设计模式:面向接口和抽象类编程,依赖接口或抽象类而不依赖具体实现 一、创建型 1、工厂方法(Factory Method) a、普通工厂:根据不同参数返回创建的不同对象。 b、工厂方法:根据不同方法...

狠一点
25分钟前
1
0
python:获取文件最后N行

#获取文件最后N行的函数 def tail(inputfile) : filesize = os.path.getsize(inputfile) blocksize = 1024 dat_file = open(inputfile, 'r') last_line = "" if filesize > blocksize : maxs......

perofu
32分钟前
2
0
JavaScript(四):注释

认识 注释的作用 是提高代码的可读性,帮助自己和别人阅读和理解你所编写的JavaScript代码,注释的内容不会在网页中显示。 注释可分为 单行注释与 多行注释 两种。 我们为了方便阅读,注释内...

Agnes2017
39分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部