# React 性能优化

# 更新原则

React组件,肯定离不开props 和 state,我们可以在props和state中存放任意类型的数据,通过改变props和state,去控制整个组件的状态,去控制整个组件的状态,当props和state发生变化的时候,(即使在render的时候没有改使用state或者props的值)React都会重新渲染整个组件,

当组件的 props 或 state 变化,React 将会构建新的 virtual DOM,使用 diff 算法把新老的 virtual DOM 进行比较,如果新老 virtual DOM 树不相等则重新渲染,相等则不重新渲染。DOM 操作是非常耗时的,这导致重新渲染也非常的耗时,因此要提高组件的性能就应该尽一切可能的减少组件的重新渲染。

但是我们以为rerender的仅仅更改的dom节点,但事实上react的更新原则是,只要组件的 props 或 state 发生了变化就会重新渲染整个组件,除了必要渲染的三个节点外,还渲染了其他不必要渲染的节点,这对性能是一个很大的浪费。如果对于复杂的页面,这将导致页面的整体体验效果非常差。因此要提高组件的性能,就应该想尽一切方法减少不必要的渲染

# 组件优化

Pure Component 如果一个组件只和 props 和 state 有关系,给定相同的 props 和 state 就会渲染出相同的结果,那么这个组件就叫做纯组件

shouldComponentUpdate shouldComponentUpdate 这个函数会在组件重新渲染之前调用,函数的返回值确定了组件是否需要重新渲染。函数默认的返回值是 true,意思就是只要组件的 props 或者 state 发生了变化,就会重新构建 virtual DOM,然后使用 diff 算法进行比较,再接着根据比较结果决定是否重新渲染整个组件。函数的返回值为 false 表示不需要重新渲染

# 优化策略1 使用PureRenderMixin

减少不必要渲染

React 官方推荐可以采用PureRenderMixin的方法,这个方法提供一个shouldComponentUpdate的实现,提供一个浅比较,将当前的props和state与下一个props和state去做比较。

但是,这个仅仅做的是浅比较,如果数据结构比较深层次的话,那么这种方法可能还是会返回false,这是一个错误的结果,我们只能通过调用forceUpate()去强制更新。

eg1: 使用插件

import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

eg2: 直接继承类 React.PureComponent,他在内部会实现shouldComponentUpdate

import React from 'react';
class FooComponent extends React.PureComponent {
  constructor(props) {
    super(props);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

# 优化策略2 使用 immutable objects

对于复杂的数据的比较是非常耗时的,而且可能无法比较,通过使用 Immutable.js 能够很好地解决这个问题,Immutable.js 的基本原则是对于不变的对象返回相同的引用,而对于变化的对象,返回新的引用。因此对于状态的比较只需要使用如下代码即可:

shouldComponentUpdate() {
	return ref1 !== ref2;
}

这类比较是非常快速的, 如何在代码中使用Immutable.js 与之带来的问题是:

  • 学习Immutable的成本
  • 原生js对象和Immutable的交互

# 性能测试

# 测试1: 渲染长列表10000条数据

方式1:使用原生的js 花费时间 15ms 左右的时间

react-002

(function() {
  console.time("start")
  var id = document.getElementById("id");


  var items = [];
  for(var i = 0; i < 9999; i++) {
    items.push(i)
  }

  items.forEach((index) => {
    var newDiv = document.createElement("div");
    var textnode = document.createTextNode(index);         // Create a text node
    newDiv.appendChild(textnode);
    id.appendChild(newDiv);
  })


  document.addEventListener("DOMContentLoaded", function(event) {
    console.log("DOM fully loaded and parsed");
    console.timeEnd("start")
  });

})();

方式2:使用React的Component 及其缓慢,大概需要8000+ms,是原生js的500倍,简直。

react-003

import React, { Component } from 'react';
import Item from  './Item';
import _ from 'lodash'

class List extends Component {

  componentDidMount() {
    console.timeEnd("name")
  }

  render() {
    let items = []
    items = _.times(10000, (index) => index)
    console.time("name")

    let showLists = items.map((item) => {
      return <Item name={item} key={item}/>
    })

    return (
      <div>
        {showLists}
      </div>
    );
  }
}

export default List;

分析:为什么这么慢? 因为实际上与js的原生每一行都是一个<div>元素不同,这里的每一个Item都是react的一个组件,并且这个组件是被react封装过的组件,里面包含了大量的js逻辑代码和程式,并不像<div>只是一个元素那么简单。所以想到这里就是一个很有意思的问题,究竟什么样的东西该被封装成一个组件,同时又没有必要去写成一个Full Component,还是说我们仅仅使用stateless funciton就好了呢。下面的例子:我们使用StateLess function会发现,确实性能比使用Full component好了很多。

方式3: 使用React的StateLess function

大概需要 700ms,与上面相比已经提高了很多了

react-004

import React, { Component } from 'react';
import NoReactItem from './Item-without-react'
import _ from 'lodash'

class List extends Component {

  componentDidMount() {
    console.timeEnd("name")
  }

  render() {

    let items = []

    items = _.times(10000, (index) => index)
    console.time("name")

    let showLists = items.map((item) => {
      return (
         <NoReactItem name={item} key={item}/>
       )
    })

    return (
      <div>
        {showLists}
      </div>
    );
  }
}
export default List;

import React from 'react';

const NoReactItem = (props) => {
  return (
    <div>
      {props.name}
    </div>
  )
}

export default  NoReactItem;

# 参考

陕ICP备20004732号-3