前面这篇文章,我已经介绍了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> -- 程墨