网上对比文章已经有好多了,不过主要是写给自己看的。
假设有这么一个页面, 父组件有一个子组件,子组件就一个纯div标签,父组件点击改变自己的state.count
:
import React from 'react';
class Children extends React.Component{
render(){
console.log('Children Update');
return (
<div>Children Component</div>
)
}
}
class App extends React.Component{
state = {
count: 0
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
render(){
const { count } = this.state;
return (
<div>
<button onClick={this.add}>加一</button>
<Children />
<p>count: { count }</p>
</div>
)
}
}
export default App;
如上,每次点击按钮,即使跟count
没有关联的Children
组件也会重新渲染,很容易影响性能。我们希望每次更新count
时,跟count
无关的组件不会重新渲染。这时候我们可以使用shoundComponentUpdate
。
ShouldComponentUpdate
shouldComponentUpdate(newProps, nextState){}
shouldComponentUpdate
接受两个参数,一个是即将更新的props
和一个即将更新的state
。当 props 或 state 发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。
如果返回值是true
就重新渲染,如果false
就不做任何处理(会调用 UNSAFE_componentWillUpdate()
,render()
和 componentDidUpdate()
),使性能得到优化。
在这里我们对比未更新前的props.count
和即将更新的props.count
值,如果一样的话就不更新,如果不一样就刷新子组件:
shouldComponentUpdate(newProps, nextState){
if(newProps.count === this.props.count) return false;
return true;
}
这样的话,即使多次触发按钮,也不会多次渲染子组件了。
再举一个例子:
页面上有一个按钮,点击之后,重新赋值count
,虽然count
的值一直是0,但是这个组件还是一直再不停的渲染。
总结:当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate
来优化。
接下来说一下缺点, 更改一下 内容:
import React from 'react';
class Children extends React.Component{
shouldComponentUpdate(nextProps, nextState){
console.log(nextProps.person.name, this.props.person.name);
if(nextProps.person.name === this.props.person.name) return false;
return true;
}
render(){
console.log('Chidlred update');
const { person, handleClick } = this.props;
return (
<div onClick={() => handleClick()}>Children Component - {person.name}</div>
)
}
}
class App extends React.Component{
state = {
person: {
name: 'sugarMei'
}
}
changeName = () => {
const person = this.state.person;
person.name = '王花花';
this.setState({
person: person
})
}
render(){
const { person } = this.state;
return (
<Children handleClick={this.changeName} person={person} />
)
}
}
export default App;
虽然每次点击之后,界面上person.name
没有发生改变。
这是由于person
是一个对象,person
其实是指向内存的指针,在shouldComponentUpdate
中使用console.log(nextProps.person.name, this.props.person.name);
;不管是nextProps.person还是this.props.person
都是指向了同一个内存,结果是一样的。
console.log(nextProps.person.name, this.props.person.name); // 王花花 王花花
使用浅拷贝(官方推荐Object.assign
)来改变name
值,也是不行的:
changeName = () => {
this.setState({
person: Object.assign(this.state.person, { name: '王花花'})
})
}
故,如果在shouldComponentUpdate
比较的值是引用类型的话,可能达不到我们想要的效果。
这里的解决方案有几种:
-
使用深拷贝:
changeName = () => { const person = JSON.parse(JSON.stringify(this.state.person)); person.name = '王花花'; this.setState({ person }) }
我们不建议在
shouldComponentUpdate()
中进行深层比较或使用JSON.stringify()
。这样非常影响效率,且会损害性能。 -
使用第三方库
immutable.js
: 深拷贝/浅拷贝本身是很耗内存, 而immutable本身有一套机制使内存消耗降到最低,但是需要花点时间去学习API
。
最后,使用shouldCompoent
可以便于我们去浅层比较一些数据之后然后来控制是否重新渲染,比较灵活,缺点时如果数据过多时,比较的逻辑可能会很长很复杂,以及对引用型数据的不太友好。
PureComponent
pureComponent
也是浅层比较,不同于shouldComponentUpdate
的是:
- 在
Component
中可以使用shouldComponentUpdate
,而在pureComponent
中不行; - 如果使用
shouldComponentUpdate
需要自己手写逻辑,但是在pureComponent
中就不用。
之前的例子:
import React from 'react';
class Children extends React.Component{
shouldComponentUpdate(newProps, nextState){
if(newProps.count === this.props.count) return false;
return true;
}
render(){
console.log('Children Update');
return (
<div>Children Component</div>
)
}
}
class App extends React.Component{
state = {
count: 0
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
render(){
const { count } = this.state;
return (
<div>
<button onClick={this.add}>加一</button>
<Children />
<p>count: { count }</p>
</div>
)
}
}
export default App;
我们修改Children
组件,继承pureComponent
:
class Children extends PureComponent{
render(){
console.log('Children Update');
return (
<div>Children Component</div>
)
}
}
这样也能确保得到同样的效果。
PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
如果pureCompent
包裹的组件中也希望引用类型数据改变的时候也会更新,那么可以借助forceUpdate
:
import React, { PureComponent } from 'react';
class Children extends PureComponent{
render(){
console.log('Children Update', this.props.person);
return (
<div>Children Component, {this.props.person.name}</div>
)
}
}
class App extends React.Component{
state = {
count: 0,
person: {
name: 'sugar'
}
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
changeName = () => {
this.setState({
person: Object.assign(this.state.person, { name: '王花花'})
})
this.child.forceUpdate();
}
render(){
const { count, person } = this.state;
return (
<div>
<button onClick={this.add}>加一</button>
<button onClick={this.changeName}>改变名字</button>
<Children person={person} ref={(child) => {this.child = child}} />
<p>count: { count }</p>
</div>
)
}
}
export default App;
Memo
React.Meno
跟React.pureComponent
差不多,区别是:
PureComponent
要依靠class
才能使用,而React.Memo
可以和函数式组件一起使用;React.Memo
可以和函数式组件一起使用,所以只比较props
;React.Memo
可以传递第二个参数,它可以改变控制对比过程;- 与 class 组件中
shouldComponentUpdate()
方法不同的是,如果 props 相等,areEqual
会返回true
;如果 props 不相等,则返回false
。这与shouldComponentUpdate
方法的返回值相反。
将Children
组件改为函数式组件,并使用Memo
:
const Children = React.memo((props) => {
console.log('Children Update');
return (
<div>Children Component</div>
)
})
参考链接:
[shouldComponentUpdate不能直接比较object](https://www.cnblogs.com/xufeimei/p/10148643.html)
如果有误,请告诉我,谢谢~