對(duì)比vue
艳狐,性能優(yōu)化對(duì)react
更加重要途戒,shouldComponentUpdate
又是react中性能重要的一個(gè)特性。
shouldComponentUpdate
是react
的一個(gè)生命周期僵驰,顧名思義喷斋,就是用于設(shè)置是否進(jìn)行組件更新
后面都使用SCU
縮寫指代shouldComponentUpdate
SCU的基本使用
說到性能優(yōu)化之前唁毒,我們要先說一下SCU
的用法:
- 它有兩個(gè)參數(shù):
- nextProps:更新后的
props
屬性 - nextState:更新后的
state
狀態(tài)
- nextProps:更新后的
- 這個(gè)生命周期中,如果返回
true
代表會(huì)觸發(fā)重新渲染星爪,為false
則不會(huì)浆西;默認(rèn)是返回true
- 在這個(gè)生命周期中,我們可以根據(jù)某個(gè)狀態(tài)顽腾,來手動(dòng)設(shè)置是否觸發(fā)重新渲染
下面這個(gè)例子中近零,設(shè)置只有計(jì)數(shù)count
狀態(tài)改變時(shí),才觸發(fā)渲染抄肖,否則則不渲染
import React, {Component} from 'react'
class SCU extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
name: '小花'
}
}
// 是否進(jìn)行更新的生命周期久信,它有兩個(gè)參數(shù),它允許我們自定義是否更新漓摩,默認(rèn)返回true
// nextProps:更新后的props屬性
// nextState: 更新后的state狀態(tài)
shouldComponentUpdate(nextProps, nextState) {
// 現(xiàn)在設(shè)置只有count改變時(shí)裙士,才觸發(fā)重新渲染
if(nextState.count === this.state.count) {
return false;
}
return true;
}
render() {
const { count, name } = this.state
// 第二個(gè)為要放至的dom元素位置
return <div>
<p>count: {count}</p>
<p>name: {name}</p>
<button onClick={this.changeCount}>累加count</button>
<button onClick = {this.changeName}>不改變count,改變其它state狀態(tài)</button>
</div>
}
changeCount = () => {
this.setState({count: this.state.count + 1})
}
changeName = () => {
this.setState({name: '小小'})
console.log(this.state.name)
}
}
export default SCU
當(dāng)改變this.state.name
時(shí)管毙,因?yàn)樵?code>SCU做了判斷腿椎,沒改變到count
的,雖然name
變了夭咬,但不會(huì)觸發(fā)視圖更新
當(dāng)改變
count
時(shí)啃炸,就發(fā)現(xiàn)視圖更新了,說明 scu
中的設(shè)置生效了什么時(shí)候用SCU?
react
在SCU
中是默認(rèn)返回true
的卓舵,也就是說南用,只要父組件更新,那么所有的子組件都會(huì)無條件跟著更新掏湾,即使這個(gè)子組件內(nèi)容沒有任何更新裹虫。
但在日常開發(fā)中,我們常常是只需要更新其中一個(gè)或幾個(gè)子組件忘巧,其它未有任何改變的子組件恒界,我們并不希望它重新渲染,這時(shí)候就要會(huì)用到SCU
來優(yōu)化砚嘴。
下面以一個(gè)例子說明:
Todolist 清單十酣,它有三個(gè)子組件:
- TodoInput:輸入框,和提交按鈕
- List:todolist际长,列表顯示組件
- Footer:一些其它的描述信息
// TodoList.js
import React, { Component } from 'react';
import TodoInput from './TodoInput'
import List from './List'
import Footer from './Footer'
export default class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
list: [
{id: 0, title: '吃飯'},
{id: 1, title: '睡覺'}
],
footer: '頁尾信息'
}
}
render() {
return <div>
<TodoInput updateList={this.updateList} />
<List list={this.state.list}/>
<hr/>
<Footer footer={this.state.footer}/>
</div>
}
updateList = (val) => {
this.setState({
list: this.state.list.concat({
id: this.state.list.length,
title: val
})
})
}
}
// TodoInput.js
import React, { Component } from 'react';
export default class TodoInput extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: ''
}
}
render() {
const { inputValue } = this.state
return <div>
<input type="text" value={inputValue} onChange={(e) => this.changeInput(e)} />
<button onClick={() => this.props.updateList(this.state.inputValue)}>新增</button>
</div>
}
changeInput = (e) => {
this.setState({
inputValue: e.target.value
})
}
}
// List.js 列表組件
export default function List(props) {
return <div>
{props.list.map(item => {
return <p key={item.id}>{item.title}</p>
})}
</div>
}
// Footer.js
import React, { Component } from 'react';
export default class Footer extends Component {
componentDidUpdate() {
console.log('Footer組件更新');
}
render() {
return <p>{this.props.footer}</p>
}
}
當(dāng)新增一項(xiàng)事項(xiàng)時(shí)耸采,發(fā)現(xiàn)Footer
組件即使沒更新內(nèi)容,也被重新渲染了
這其實(shí)就是一種性能的浪費(fèi)工育,因?yàn)楸旧硎菦]必要渲染的虾宇,只有Footer
的內(nèi)容footer
更新時(shí),我們才需要更新這個(gè)組件如绸,所以可以在SCU
中優(yōu)化:
// Footer.js
shouldComponentUpdate(nextProps, nextState) {
// 只有footer改變時(shí)嘱朽,才重新渲染
if(nextProps.footer !== this.props.footer) {
return true;
}
return false;
}
此時(shí)旭贬,再去新增事項(xiàng)時(shí),就不會(huì)觸發(fā)Footer
組件重新渲染了
SCU必須配合不可變值一起使用
這是因?yàn)槿绻皇褂貌豢勺冎禃r(shí)搪泳,那么對(duì)象的改變其實(shí)還是引用同一個(gè)地址稀轨,那么在SCU
做對(duì)比時(shí),就不可避免要一次性遞歸比較里面所有的內(nèi)容:
- 使用可變量來修改時(shí)
在SCU
時(shí)岸军,要一次性遞歸遍歷對(duì)象的每一項(xiàng)才能準(zhǔn)確判斷出兩者是否相等奋刽,下面是使用lodash
庫的isEqual
方法來一次性遞歸遍歷,當(dāng)對(duì)象層級(jí)很深時(shí)艰赞,可想而知佣谐,是極其浪費(fèi)性能的
changeList = () => {
// 使用可變量,SCU要付出極大代價(jià)方妖,不建議使用
this.state.list.push({
id: this.state.list.length,
title: 'xxx'
})
this.setState({
list: this.state.list
})
}
shouldComponentUpdate(nextProps, nextState) {
// _isEqual要深層遍歷每一項(xiàng)是否相等狭魂,才可以準(zhǔn)確判斷
if(_.isEqual(nextState.list !== this.state.list) {
return true;
}
return false;
}
- 使用不可變量來修改時(shí)
使用不可變量來修改時(shí),因?yàn)樾聦?duì)象使用的是新的引用地址吁断,所以在SCU
比對(duì)時(shí)趁蕊,只需要淺對(duì)比坞生,就可以判斷出兩者的不同
updateList = () => {
// 使用不可變量
this.setState({
list: this.state.list.concat({
id: this.state.list.length,
title: 'xxx'
})
})
shouldComponentUpdate(nextProps, nextState) {
// 使用不可變量時(shí)使用SCU
if(nextState.list !== this.state.list) {
return true;
}
return false;
}
這也是為什么一直強(qiáng)調(diào)setState
時(shí)要遵循react immutable
不可變值理念的重要原因
react為什么不直接一開始就SCU優(yōu)化仔役,而是給用戶保留設(shè)置?
因?yàn)椴荒鼙WC用戶一定遵循immutable
理念的寫法是己,如果用戶使用可變量來修改時(shí)又兵,react
在SCU
時(shí)就要付出極大性能,所以react
并不選擇在一開始就在SCU
優(yōu)化卒废,而是交給開發(fā)者自己選擇是否要使用SCU
優(yōu)化
針對(duì)于使用了不可變值的組件沛厨,react
其實(shí)提供了PureComponent
實(shí)現(xiàn)SCU
淺比較的優(yōu)化方案
SCU總結(jié)
-
SCU
默認(rèn)返回true
,即react
默認(rèn)重新渲染所有子組件 - 必須配合不可變值一起使用摔认,不然會(huì)有性能問題
-
SCU
優(yōu)化并不一定在一開始就要使用逆皮,而是在遇到性能問題時(shí),我們才會(huì)考慮使用它