tldr;优先使用
PureComponent
,并且永远不要改变数据对象(或者使用其它最佳实践)
何时使用Component或PureComponent?
我使用PureComponent
已经有一段时间,这基于它是一个更具性能的组件版本。
事实证明这是真的,但是性能优势带来了一些必要前提。
我们来深入研究PureComponent
并且理解为什么我们应该去使用它。
Component
个PureComponent
只有一个区别
PureComponent
和Component
几乎完全一样,唯一的区别是PureComponent
为你处理shouldComponentUpdate
事件。
当props
或state
发生变化,PureComponent
会对两者都做浅比较
;而Component
则不会对两者的新旧值进行比较。所以,无论何时调用shouldComponentUpdate
,组件都会默认触发re-render
。
译者注:
shouldComponentUpdate
默认返回true
,一定触发re-render
浅比较 101
当对props
和state
的新旧值进行比较时,浅比较
只会检查基本数据类型
的值是否相等(比如:1
等于1
或者true
等于true
),复杂如对象
和数组
也是如此,不过是去比较引用值
。
不要改变数据
你可能听过,不要对props
和state
中的对象
和数组
进行改变。如果你在父组件对对象
进行了改变,你的纯
子组件并不会更新。尽管上游的值发生了改变,但是子组件只会对props
进行引用值
的比较并且无法检测到区别。
译者注:这里改变,指
不改变
对象引用的操作
而正确的做法是,借助ES6
的object
新特性、array
的扩展运算符
或者使用不可变工具库
来返回新对象
。
译者注:这里改变,指
改变
对象引用的操作
以上操作会造成性能问题吗?
对基本数据类型
的值和引用数据类型
的引用值
比较是个极其廉价的操作。如果你有一组子组件列表并且其中一个更新了,相比于重新渲染所有,在每一个子组件上进行props
和state
的比较仍然是一个非常快的过程。
其他一些注意点
不要在render
中的函数绑定值
比如你父组件里面有一组子组件列表,每一个都传给父组件方法一个唯一的参数。为了绑定这个参数,你可能这样做:
<CommentItem likeComment={() => this.likeComment(user.id)} />
复制代码
问题是每一次父组件的render
方法调用时,一个新函数(伴随着新的引用)就会被创建并且传递给likeComment
属性。这会导致一些副作用:每一个子组件的props
都发生改变,最终导致他们全部重新渲染,哪怕数据本身并没有发生变化。
解决这个问题的方法是,仅仅传入父组件原型链方法的引用给子组件。子组件的likeComment
属性永远都有相同的引用值
并且永远不会引起不必要的re-render
。
<CommentItem likeComment={this.likeComment} userID={user.id} />
复制代码
那么子组件仅需要创建一个类方法并且引用它的props
即可:
class CommentItem extends PureComponent {
...
handleLike() {
this.props.likeComment(this.props.userID)
}
...
}
复制代码
不要在render
方法中获取数据
考虑你的profile
组件需要显示用户的10个最受欢迎的文章:
render() {
const { posts } = this.props
const topTen = posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
return //...
}
复制代码
组件每次re-render
时,topTen
变量都会是一个全新的引用值
,哪怕posts
变量值没有发生改变或者slice
的结果也没有发生变化。但这仍然会对文章列表产生没有必要的re-render
。
你可以缓存你获取的数据来解决这个问题。比如,把获取数据操作放入state
中,并且仅在posts
属性发生更新时更新。
componentWillMount() {
this.setTopTenPosts(this.props.posts)
}
componentWillReceiveProps(nextProps) {
if (this.props.posts !== nextProps.posts) {
this.setTopTenPosts(nextProps)
}
}
setTopTenPosts(posts) {
this.setState({
topTen: posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
})
}
复制代码
如果你使用Redux
,考虑使用reselect来创建选择器
去组合并且缓存你获取的数据。
最后
只要你明确以下两点,相比于Component
,使用PureComponent
就很安全:
-
改变数据通常是不好的,尤其是使用
PureComponent
时会让问题更复杂 -
如果你在
render
方法中创建了新函数
,对象
或数组
,那么你的做法是错的
感谢Daniel Min将这篇文章翻译至Korean