黄qiong

2017-12-24 18:59

React性能优化 -- 利用React-Redux

本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载

React渲染机制

前面这篇文章,我已经介绍了React的渲染机制.

简单来说,就是在页面一开始打开的时候,React会调用render函数构建一棵Dom树,在state/props发生改变的时候,render函数会被再次调用渲染出另外一棵virtula Dom,接着,React会用对两棵树进行对比,计算出对DOM树的最少修改,然后批量改动。

找出优化点

注意这里面,如果可以在渲染virtual DOM之前就可以判断渲染结果不会有变化,那么可以直接不进行virtual DOM的渲染和比较,速度会更快。

shouldComponentUpdate

怎么可以做到呢?

这里需要用到React生命周期里的shouldComponentUpdate,当这个函数返回false的时候,DOM tree直接不需要重新渲染,从而节省大量的计算资源。 由于每个React组件的逻辑都有自己的特点,所以需要根据组件逻辑来定制shouldComponentUpdate函数的行为.

以官网的TodoList为例: http://cn.redux.js.org/docs/basics/ExampleTodoList.html

import React, { Component, PropTypes } from 'react'

export default class Todo extends Component {
  render() {
    return (
      <li
        onClick={this.props.onClick}
        style={{
          textDecoration: this.props.completed ? 'line-through' : 'none',
          cursor: this.props.completed ? 'default' : 'pointer'
        }}>
        {this.props.text}
      </li>
    )
  }
}

Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  text: PropTypes.string.isRequired,
  completed: PropTypes.bool.isRequired
}

对于这个todo来说,只要completedtext都没有发生改变,那么这个组件渲染的结果就不会发生改变,因此,shouldComponentUpdate就可以这样写:

shouleComponentUpdate(nextProps, nextState) {
  return (nextProps.completed !== this.props.completed) || 
         (nextProps.text !== this.props.text)
}

两个问题

接下来就有两个问题需要我们思考了:

  1. 是不是所有的组件都需要这个shouldComponentUpdate
  2. 每个需要shouldComponentUpdate的组件都需要自己写逻辑是不是太麻烦了

问题一:

关于这个问题,我在前一篇文章其实已经作答,使用React Pref,或者why-did-you-update都可以找到无需被重新渲染的组件,这个组件就是需要使用shouldComponetUpdate优化的组件。

问题二:

确实麻烦啊,能偷懒就偷懒的我们怎么能忍受。 所以可以直接使用React-Redux的connect帮助我们.

React-Redux的connect其实会自动做一个对props的比较。过程如下:

简而言之,它会根据传入进来的propsstate通过各种计算得到nextProps跟上一个props做比较,如果不相等,shouldComponentUpdate才会返回false

所以,有了它,todo.js就可以这样写

import React, { Component, PropTypes } from 'react'
class Todo extends Component {
  render() {
    return (
      <li
        onClick={this.props.onClick}
        style={{
          textDecoration: this.props.completed ? 'line-through' : 'none',
          cursor: this.props.completed ? 'default' : 'pointer'
        }}>
        {this.props.text}
      </li>
    )
  }
}


Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  text: PropTypes.string.isRequired,
  completed: PropTypes.bool.isRequired
}

export default connect()(Todo);

跟踪问题

这里我使用React Pref跟踪问题,加上之后,查看控制台,可以看到浪费的渲染时间由TodoList -> Todo转变到了Connect(Todo)> Todo,要理解这个现象就要理解connectshouldComponentUpdate的实现方式。

注意:

这里对props的对比只是一个浅比较,所以要让react-redux认为前后的对象是相同的,必须指向同一个js对象。

例如:

<Foo style={color: 'red'}>

这样每次传入进来的style都是一个新的对象,所以即使里面的值相等,react-redux的浅比较仍然认为它不等需要重新渲染。

再回来看看TodoList里是怎么用Todo的

 <ul>
    {this.props.todos.map((todo, index) =>
      <Todo {...todo}
            key={index}
            onClick={() => this.props.onTodoClick(index)} />
    )}
 </ul>

不知道大家发现了没有?这里的每一个onClick都是一个新的函数,即使Todo被装备了shouldComponentUpdate的实现,浅比较的时候props总是不相等,依旧躲不过每次更新都要被重新渲染的命运。

如何改进

有两种做法:

方法一:

先来看看这个onTodoClick是怎么被一层层传下来的:

// App.js
<TodoList
  todos={visibleTodos}
  onTodoClick={index =>
  dispatch(toggleTodo(index))}
/>

//  TodoList.js
 <Todo {...todo}
   key={index}
   onClick={() => this.props.onTodoClick(index)}
/>

// todo.js
 <li
    onClick={this.props.onClick}
    style={{
      textDecoration: this.props.completed ? 'line-through' : 'none',
      cursor: this.props.completed ? 'default' : 'pointer'
    }}>
    {this.props.text}
  </li>

可以在TodoList的时候,不构造匿名函数直接将onTodoClick传下来,然后index可以放在新加的属性id里。

//  TodoList.js
 <Todo {...todo}
   key={index}
   *id={item.id}*
   onClick={onTodoClick}
/>

// todo.js的mapDispatchToProps需要做对应的改变

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => ownProps.onToggle(ownProps.id)
})

方法二:

直接让TodoList不要给todo传递任何函数类型的prop,点击事件完全由todo组件自己搞定。

// TodoList只需要穿一个id出来
 <Todo {...todo}
   key={index}
   *id={item.id}*
/>

// todo中,自己通过react-redux派发action即可

const mapDispatchToProps = (dispatch, ownProps) => {
  const {id} = this.props;
  return {
    onClick: () => dispatch(toggleTodo(id))
  }
}

对比两种方式,其实对于todo来说,都需要使用react-redux,都需要todoList传入一个id,区别只在于actions是由父组件还是有组件自己导入。

相比而言,没有必要一层层传递这个action,第二种方式让todo处理自己的一切事务,更符合高内聚的要求。

总结

说了那么多,总之就是通过React Pref帮我们找到需要优化的组件,然后用connect帮助我们做优化偷个懒。

参考: <深入浅出React和Redux> -- 程墨

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。