前面這篇文章,我已經介紹了React的渲染機制.html
簡單來講,就是在頁面一開始打開的時候,React會調用render函數構建一棵Dom樹,在state/props發生改變的時候,render函數會被再次調用渲染出另一棵virtula Dom,接着,React會用對兩棵樹進行對比,計算出對DOM樹的最少修改,而後批量改動。
注意這裏面,若是能夠在渲染virtual DOM以前就能夠判斷渲染結果不會有變化,那麼能夠直接不進行virtual DOM的渲染和比較,速度回更快。react
怎麼能夠作到呢?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
關於這個問題,我在前一篇文章其實已經做答,使用React Pref
,或者why-did-you-update
均可以找到無需被從新渲染的組件,這個組件就是須要使用shouldComponetUpdate優化的組件。this
確實麻煩啊,能偷懶就偷懶的咱們怎麼能忍受。
因此能夠直接使用React-Redux的connect幫助咱們.spa
React-Redux的connect其實會自動作一個對props的優化比較。過程以下:3d
簡而言之,它會根據傳入進來的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的實現方式。
注意:
這裏對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> -- 程墨