React组件性能优化

一、PureRender

影响网页性能最大的因素是浏览器的重绘和排版,React的 Virtual DOM 就是为了尽量减少浏览器的重绘和重排版。要优化性能,就需要提高 Virtual DOM 的效率。结合React的渲染过程来看,就是要 防止不必要的渲染。 对此,React提供了一个便捷方法:PureRender。要理解PureRender中的Pure,首先需要理解纯函数。

纯函数的三大原则:

  • 给定相同的输入,总是返回相同的输出。
  • 过程没有副作用:在纯函数中不能改变外部状态
  • 没有额外的状态依赖:方法内的状态都只在方法的生命周期内存活,意味着我们不能在方法内使用共享变量。

React组件本身就是纯函数。React的createElement方法保证了组件是纯洁的,即传入指定props得到一定的Virtual DOM,整个过程是可预测的。PureRender中的 Pure 指的就是组件满足纯函数的条件,即组件的渲染是被相同的 props 和 state 渲染进而得到相同的结果。

PureRender的本质是:重新实现了shouldComponentUpdate生命周期方法,让当前传入的props和state与之前的作 浅比较,如果返回false,组件就不执行render方法。

为什么是浅比较呢?因为深比较太昂贵了。故 PureRender 对object只作了引用比较,并没有进行值比较。在PureRender的源代码中,只对新旧props进行了浅比较。其示例代码如下:

function shallowEqual(obj, newObj){
    if(obj === newObj){
        return true;
    }

    const objKeys = Object.keys(obj);
    const newObjKeys = Object.keys(newObj);
    if(objKeys.length !== newObjKeys.length){
        return false;
    }

    //关键代码,值关注props中每一个是否相等,无需深入判断
    return objKeys.every(key=>{
        return newObj[key] === obj[key];
    })
}

1、运用PureRender

利用createClass构建组件时,可以使用官方的插件react-addons-pure-render-mixin。如下:

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

React.createClass({
    mixins: [PureRenderMixin],

    render(){
        return 
foo
; } });

用ES6 classes语法(不支持mixin)一样可以使用这个插件:

import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

class APP extends Component {
    constructor(props){
        super(props);

        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind;(this);
    }
    render(){
        return 
foo
; } }

当然,也可以使用decorator来实现,其中pure-render-deecorator库已经实现了这个功能。

2、优化PureRender

浅比较可以覆盖的场景并不太多,当props或state中有以下几种情况时,无论如何都会触发PuerRender为true(触发组件重新渲染).

(1)直接将props值设置为对象或数组

例如:


实际上在React中,每次调用React组件其实都会创建新的组件。因此,就算每次传入的对象或数组的值是相同的,它们的引用地址却是不一样的。也即是说,上面示例中每次渲染的style其实都是一个新的对象。为style prop设置一个默认值(空对象)也是同样的道理:


解决方法:提前赋值成常量,不直接使用字面量即可。

const defaultStyle = {} ;

这个方法将默认值保存成了同一份引用,故而避免上述问题。

同理,在props中为对象或数据计算新值同样会触发PureRender为true。

item.val > 30) } />

我们可以马上想到:始终让对象或数组保持在内存中就可以增加命中率。但 保持对象引用 不符合函数式编程的原则,会为函数带来副作用。后面介绍的Immutable.js优雅的解决了这个问题。

(2)设置props方法并通过事件绑定在元素上

示例:

constructor(props){
    super(props);

    this.handleChange = this.handleChange.bind(this);
}

handleChange(e){
    this.props.update(e.target.value);
}

render(){
    return 
}

此处的优化思路为:在构造器内进行事件绑定,避免每次都绑定事件。如果绑定方法需要传递参数,可以考虑通过抽象子组件或改变现有数据结构。

(3)设置子组件

对于设置了子组件的React组件,在调用shouldComponentUpdate时,均会返回true。

class ItemParent extends Component {
    render(){
        return(
            
                 hello 
            
        );
    }
}
上面的Item组件相当于:

显然,Item组件在任何情况下都会重新渲染。要避免这种情况,需要将判断提到父级。也就是给ItemParent设置PureRender。

import React, { Component } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';

class ItemParent extends Component {
    constructor(props){
        super(props);
        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);b
    }
    render(){
        return(
            
                 hello 
            
        );
    }
}

二、Immutable

在传递数据时,可以直接使用Immutable Data来进一步提升组件的渲染性能。

Js中的对象一般是可变的(mutable),因为使用了引用赋值,新的对象简单引用了原始对象,改变新的对象会影响到原始对象。使用引用赋值是为了节约内存,但当应用复杂后,就造成了非常大的隐患。为了解决这个问题,一般的做法是使用深拷贝或浅拷贝来避免被修改,但这样又会造成CPU和内存的浪费。Immutable可以很好的解决这些问题。

1、Immutable Data

Immutable Data就是一旦创建,就不能更改的数据。对Immutable对象进行修改、添加或删除操作,都会返回一个新的Immutable对象。

Immutable的原理:持久化的数据结构,即:使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有节点复制一遍带来的性能损耗,Immutable使用了结构共享,即:如果对象树中的一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享。

2、Immutable的优缺点

优点:

  1. 降低了“可变”带来的复杂性:值不可变。
  2. 节省内存:使用共享结构尽量复用内存,没有被引用的对象会被垃圾回收。
  3. 非常便于实现撤销/重做、粘贴/复制、时间旅行等功能:因为每次数据都是不一样的,只要把这些数据放在一个数组中存储起来,想回退到哪里,就拿出对应的数据,很容易开发撤销/重做功能。
  4. 并发安全:数据不可变,就不再需要并发锁。
  5. 拥抱函数式编程:Immutable本身就是函数式编程中的概念。只要输入一致,输出必然一致。这样开发的组件便于调试和组装。

缺点:

Immutable最大的问题就是:容易与原生对象发生混淆。虽然Immutable已经尽量把API设计得与原生对象相似,但还是很难区分 Immutable对象 还是 原生对象。

Immutable中的 Map 与 List 虽然对应的是 JS 中的 Object 和 Array,但操作完全不同。比如取值时要用 map.get('key') 而非 map.key ,要用 array.get(0) 而非array[0]。另外,Immutable每次修改都会返回新对象,很容易忘记赋值

使用第三方库时,一般需要使用原生对象,同样容易忘记转换对象

以下办法可以避免类似问题的发生:

  • 使用FlowType 或 TypeScript 静态类型检查工具
  • 约定变量命名规则,如所有的Immutable类型对象都以 $$ 开头
  • 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.list 来创建对象,可以避免Immutable对象和原生对象间的混用。

3、Immutable.js

两个 Immutable 对象可以使用 === 比较,这样是直接比较内存地址,性能最好。但是即使是两个对象的值是一样的,也会返回false。

    let map1 = Immutable.Map({a:1, b:2});
    let map2 = Immutable.Map({a:1, b:2});
    map1 === map2 // false

为了直接比较对象的值,Immutable 提供了 Immutable.is 来作 “值比较”:

Immutable.is(map1, map2) // true

Immutable.is 比较的是两个对象的 hashCode 或 valueOf (对于JS对象)。Immutable内部使用了 trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样做避免了深度遍历比较,性能非常好。

React做性能优化最常用的是 shouldComponentUpdate, 但它默认返回 true,即始终会执行 render 方法,然后做 Virtual DOM 比较,得出是否需要做真实 DOM 的更新,这往往会带来很多不必要的渲染。当然,也可以在 shouldComponentUpdate中使用深拷贝和深比较来避免不必要的 render, 但对性能消耗较大。Immutable.JS提供了简洁高效的判断数据是否变化的方法,只需 === 和 Immutable.is 比较就能知道是否需要执行render, 这个操作几乎零成本,可以极大提高性能。

三、key

子组件如果是一个数组或者迭代器的话,那么必须有一个唯一的 key 属性,用于标识当前项的唯一性。key 相同的情况下,React只会渲染第一个相同 Key 的项。

key用于做 Virtual DOM diff,可以提高性能。key的取值原则为:独一无二,能不用遍历或随机值就不用,除非列表内容也并不是唯一的表示,且没有可以相匹配的属性。

你可能感兴趣的:(React组件性能优化)