文档章节

react之性能调优

b
 boySpray
发布于 2017/09/08 16:16
字数 2541
阅读 14
收藏 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
320
2
react/react-native性能优化

写在前面 本文适合新手入门,如果是react老玩家可以或者查漏补缺。 这里就说一些常见的新手误区以及优化方法: 为何需要优化 笔者一直觉的性能优化是一个累积的过程,贯穿在你所写的每一行代...

ZoenLeo
2018/12/14
0
0
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
iOS开发者的福音,史上最全React Native学习路线

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

iOS小迷糊
2018/01/05
0
0
兄弟们,时代变了

献给默默无闻,奋斗在第一线的苦逼程序员们! 起因 无意间翻看了之前在Evernote的关于服务器端记录的开发笔记,感触良多。 2009-2010的上面记录的大多都是关于Nginx、Apache、MySQL、Linux、...

程序员孟帅
2016/02/16
16K
103

没有更多内容

加载失败,请刷新页面

加载更多

vue 对对象的属性进行修改时,不能渲染页面 vue.$set()

我在vue里的方法里给一个对象添加某个属性时,我console.log出来的是已经更改的object ,但是页面始终没有变化 原因如下: **受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),...

Js_Mei
今天
1
0
开始看《Java学习笔记》

虽然书买了很久,但一直没看。这其中也写过一些Java程序,但都是基于IDE的帮助和对C#的理解来写的,感觉不踏实。 林信良的书写得蛮好的,能够帮助打好基础,看得出作者是比较用心的。 第1章概...

max佩恩
昨天
12
0
Redux 三大原则

1.单一数据源 在传统的MVC架构中,我们可以根据需要创建无数个Model,而Model之间可以互相监听、触发事件甚至循环或嵌套触发事件,这些在Redux中都是不被允许的。 因为在Redux的思想里,一个...

wenxingjun
昨天
8
0
跟我学Spring Cloud(Finchley版)-12-微服务容错三板斧

至此,我们已实现服务发现、负载均衡,同时,使用Feign也实现了良好的远程调用——我们的代码是可读、可维护的。理论上,我们现在已经能构建一个不错的分布式应用了,但微服务之间是通过网络...

周立_ITMuch
昨天
5
0
XML

学习目标  能够说出XML的作用  能够编写XML文档声明  能够编写符合语法的XML  能够通过DTD约束编写XML文档  能够通过Schema约束编写XML文档  能够通过Dom4j解析XML文档 第1章 xm...

stars永恒
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部