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

React渲染機制

前面這篇文章,我已經介紹了React的渲染機制.html

簡單來講,就是在頁面一開始打開的時候,React會調用render函數構建一棵Dom樹,在state/props發生改變的時候,render函數會被再次調用渲染出另一棵virtula Dom,接着,React會用對兩棵樹進行對比,計算出對DOM樹的最少修改,而後批量改動。

找出優化點

注意這裏面,若是能夠在渲染virtual DOM以前就能夠判斷渲染結果不會有變化,那麼能夠直接不進行virtual DOM的渲染和比較,速度回更快。react

shouldComponentUpdate

怎麼能夠作到呢?redux

這裏須要用到React生命週期裏的shouldComponentUpdate,當這個函數返回false的時候,DOM tree直接不須要從新渲染,從而節省大量的計算資源。
因爲每一個React組件的邏輯都有本身的特色,因此須要根據組件邏輯來定製shouldComponentUpdate函數的行爲.segmentfault

以官網的TodoList爲例:
http://cn.redux.js.org/docs/b...函數

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來講,只要completed跟text都沒有發生改變,那麼這個組件渲染的結果就不會發生改變,所以,shouldComponentUpdate就能夠這樣寫:優化

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

兩個問題

接下來就有兩個問題須要咱們思考了:ui

  1. 是否是全部的組件都須要這個shouldComponentUpdate
  2. 每一個須要shouldComponentUpdate的組件都須要本身寫邏輯是否是太麻煩了

問題一:

關於這個問題,我在前一篇文章其實已經做答,使用React Pref,或者why-did-you-update均可以找到無需被從新渲染的組件,這個組件就是須要使用shouldComponetUpdate優化的組件。this

問題二:

確實麻煩啊,能偷懶就偷懶的咱們怎麼能忍受。
因此能夠直接使用React-Redux的connect幫助咱們.spa

React-Redux的connect其實會自動作一個對props的優化比較。過程以下:3d

clipboard.png

簡而言之,它會根據傳入進來的props,state經過各類計算獲得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,要理解這個現象就要理解connect裏shouldComponentUpdate的實現方式。

注意:

clipboard.png

這裏對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能夠放在新加的屬性index裏。

// 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> -- 程墨

相關文章
相關標籤/搜索