文档章节

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
已有Android工程 集成React Native 的那些事

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

quanke
2017/02/28
0
0
react-native内存优化--图片内存

react-native在android中使用fresco来加载图片。说到fresco很多人都会为之欢呼,总比各种轮子内存调优的好。不过在react-native中在它的使用fresco还是稍显随意,如果一个页面是典型的图列表...

obaniu
2016/10/17
733
0
React基本概念(二)

1.组件的生命周期 实例化: 实例初次被创建,首次渲染时所调用的生命周期方法与其他各个后续实例被创建时所调用的方法略有不同。 在首次使用一个组件类时,下面方法依次调用顺序: --getDef...

Turnsole1
2017/11/27
0
0
兄弟们,时代变了

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

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

没有更多内容

加载失败,请刷新页面

加载更多

Docker 基础及安装

Docker 是一个开源工具,它可以让创建和管理 Linux 容器变得简单。容器就像是轻量级的虚拟机,并且可以以毫秒级的速度来启动或停止。Docker 帮助系统管理员和程序员在容器中开发应用程序,并...

PeakFang-BOK
26分钟前
0
0
Vue.js 内置指令

Vue.js 的指令是带有特殊前缀 “v-“ 的 HTML 特性。它绑定一个表达式,并将一些特性应用到 DOM 上。 一、基本指令 1.1 v-cloak v-cloak 不需要表达式,它会在 Vue 实例结束编译时从绑定的 ...

Mr_ET
32分钟前
1
0
怎么样在谷歌找文章

使用这些前缀:(不懂英文经常在谷歌搜出些产品词——明明我要文章——,其实加些前缀就出来了 ,如tips amazon tool,step amazon tool) top 10 ... 10 tips to ... what is ... how to ... ...

阿锋zxf
35分钟前
0
0
缓存与数据库的双写一致性问题

数据库与缓存的双写一致性问题 cache aside pattern 数据库与缓存的双写一致性 为什么是先删除缓存再更新数据库,而不是反过来 并发读写下的一致性问题 总结: 读请求和写请求串行化,串到一个...

grace_233
52分钟前
1
0
详解java并发包源码之AQS独占方法源码分析

AQS 的实现原理 学完用 AQS 自定义一个锁以后,我们可以来看一下刚刚使用过的方法的实现。 分析源码的时候会省略一些不重要的代码。 AQS 的实现是基于一个 FIFO 队列的,每一个等待的线程被封...

小刀爱编程
56分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部