一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
前言
大家好呀,我是L同学。在上篇文章中,我们讲到了只更新了App组件中的数据,没有改变其他组件,但是它们都会被重新render,进行diff算法,这会造成性能低下。它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法。在这篇文章中,我们将学习到以上问题的解决方法。
shouldComponentUpdate
我们可以通过shouldComponentUpdate方法来控制render的调用。现在我们来学习下shouldComponentUpdate的使用场景。
import React, {Component} from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0,
message: 'hello world'
}
}
render() {
console.log('App render函数被调用');
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
changeText() {
this.setState({
message: 'hello react'
})
}
}
我们可以看到页面上显示了counter和两个按钮。
当我们点击改变文本按钮的时候,message的值发生了改变,所以render监听到了state的改变,就会重新调用render方法。
在jsx中并没有依赖这个message,那么message的改变不应该引起重新render。这时我们可以使用shouldComponentUpdate这个生命周期方法。
shouldComponentUpdate这个生命周期方法接收两个参数,并且需要有返回值。
这个方法有两个参数。第一个参数是nextProps,修改之后,最新的props属性。第二个参数是nextState修改之后,最新的state属性。
这个方法返回值是一个Boolean类型。返回值是true,就需要调用render方法。返回值是false,就不需要调用render方法。默认返回的是true,也就是只要state发生改变,就会调用render方法。
shouldComponentUpdate(nextProps, nextState) {
if (this.state.counter !== nextState.counter) {
return true
}
return false
}
当我们点击+1按钮时,会重新render。
当我们点击改变文本按钮时,不会重新render。
如果我们所有的类都实现shouldComponentUpdate生命周期方法的话,会增加非常多的工作量。另一种方法,我们可以让class继承自PureComponent。
import React, {PureComponent, Component} from 'react'
function Header() {
console.log('Header被调用');
return <h2>我是Header组件</h2>
}
class Banner extends Component {
render() {
console.log('Banner render函数被调用');
return <h3>我是Banner组件</h3>
}
}
function ProductList() {
console.log('ProductList被调用');
return (
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
)
}
class Main extends PureComponent {
render() {
console.log('Main render函数被调用');
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
}
function Footer() {
console.log('Footer被调用');
return <h2>我是Footer组件</h2>
}
export default class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<Header/>
<Main/>
<Footer/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
可以看到当点击+1按钮时,只有Header组件和Footer组件被重新渲染了。
对于类组件我们可以使用shouldComponentUpdate生命周期方法,还可以让class继承PureComponent。那么对于Header组件、Footer组件这种函数式组件该怎么解决呢?
我们需要使用一个高阶组件memo。将这些函数式组件都通过memo函数进行一层包裹。
首先要从react中导入memo。
import React, {PureComponent, Component, memo} from 'react'
然后使用memo函数将这些函数式组件进行包裹,返回的是新的组件。
const MemoHeader = memo(function Header() {
console.log('Header被调用');
return <h2>我是Header组件</h2>
})
const MemoFooter = memo(function Footer() {
console.log('Footer被调用');
return <h2>我是Footer组件</h2>
})
最后App组件的render函数调用的是新的组件。
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<MemoHeader/>
<Main/>
<MemoFooter/>
</div>
)
}
我们可以看到点击+1按钮后,没有重新调用其他组件。