react之性能调优
博客专区 > boySpray 的博客 > 博客详情
react之性能调优
boySpray 发表于2个月前
react之性能调优
  • 发表于 2个月前
  • 阅读 2
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 学生专属云服务套餐 10元起购>>>   

叙述

        此篇属个人的 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 期望见到的效果很是相似。

        

共有 人打赏支持
粉丝 0
博文 5
码字总数 4483
×
boySpray
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: