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/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)
}
两个问题
接下来就有两个问题需要我们思考了:
- 是不是所有的组件都需要这个shouldComponentUpdate
- 每个需要shouldComponentUpdate的组件都需要自己写逻辑是不是太麻烦了
问题一:
关于这个问题,我在前一篇文章其实已经作答,使用React Pref
,或者why-did-you-update
都可以找到无需被重新渲染的组件,这个组件就是需要使用shouldComponetUpdate优化的组件。
问题二:
确实麻烦啊,能偷懒就偷懒的我们怎么能忍受。
所以可以直接使用React-Redux的connect帮助我们.
React-Redux的connect其实会自动做一个对props的优化比较。过程如下:
简而言之,它会根据传入进来的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> -- 程墨
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。