首先歡迎大家關注我的Github部落格,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關注呀!react的部分持續更新中...
React15.3中新加了一個PureComponent類,取代之前的PureRenderMixin , PureComponent可以進行React效能優化,減少不必要的render渲染次數,使用時只要把繼承類從Component換成PureComponent。
PureComponent的原理是繼承了Component類,自動載入shouldComponentUpdate函式,當元件更新時,shouldComponentUpdate對props和state進行了一層淺比較,如果元件的props和state都沒有發生改變,render方法就不會觸發,省去Virtual DOM的生成和對比過程,達到提升效能的目的。
下面從PureComponent的使用和原始碼來徹底瞭解它.
shouldComponentUpdate優化效能
在PureComponent之前,我們經常看到優化react效能最常見的手段之一就是在react的生命週期函式shouldComponentUpdate裡判斷props或state的資料是否發生變化,通過返回ture(更新)和false(不更新)來阻止不必要的render.
首先來看看react產生不必要渲染的一個場景:
用create-react-app來初始化react的執行環境,然後在App.js檔案裡建立一個定時任務,每隔一秒更新資料:
import React, { Component } from 'react'
import ShouldComponentUpdateList from './ShouldComponentUpdateList'
// 容器元件
export default class App extends Component {
state = {
data: []
}
componentDidMount() {
// 定時任務,每隔一秒更新資料
setInterval(() => {
this.setState({
data: [
{ title: 'react line 1' },
{ title: 'react line 2' },
]
})
}, 1000)
}
render() {
return(
<div>
{
this.state.data.map((item, index) => (
<ShouldComponentUpdateList key={index} list={item} />
))
}
</div>
)
}
}
複製程式碼
ShouldComponentUpdateList元件內容為:
export default class List extends Component {
render() {
console.log('list render')
return(
<div>{this.props.list.title}</div>
)
}
}
複製程式碼
命令列執行npm start,瀏覽器檢視輸出:
發現控制檯每隔一秒都會輸出list render
,明明資料沒有發生變化,但是react還是發生渲染,造成了不必要的渲染浪費。
只需要在shuoldComponentUpdate里加上判斷,再次檢視輸出結果,定時任務的資料沒有發生改變,不會再渲染render函式:
export default class List extends Component {
// 在shuoldComponentUpdate裡判斷props傳遞的資料沒有發生變化,則不需要render
shouldComponentUpdate(nextProps) {
// 返回值為true則render,為false則不render
if(nextProps.list.title === this.props.list.title) {
return false
}
return true
}
render() {
console.log('list render')
return(
<div>{this.props.list.title}</div>
)
}
}
複製程式碼
PureComponent使用
除了使用shouldComponentUpdate來判斷是否需要更新元件,還可以用PureComponent, PureComponent實際上自動載入shouldComponentUpdate函式,當元件更新時,shouldComponentUpdate對props和state進行了一層淺比較.
新建PureComponentList元件,用PureComponent代替Component:
import React, { PureComponent } from 'react'
export default class List extends PureComponent {
render() {
console.log('list render')
return(
<div>{this.props.list.title}</div>
)
}
}
複製程式碼
在App元件中傳入:
this.state.data.map((item, index) => (
<PureComponentList key={index} list={item}/>
))
複製程式碼
然後檢視瀏覽器輸出結果,驚奇地發生,PureComponent並沒有阻止不必要render,這是為什麼呢?因為前面我們說到PureComponent的shouldComponentUpdate只對props和state進行淺比較,也就是this.props = { list: { title: 'react line1' } }
,nextProps = { list: { title: 'react line1' } }
,作淺比較的話this.props當然不等於next.props.
為了更清晰地找到原因,我們先來看看PureComponent的原始碼.
PureComponent原始碼
首先找到PureComponent這個函式,在建構函式和原型上分別繼承了Component的屬性和方法:
export default function PureComponent(props, context) {
Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare(nexProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shollowEqual(this.state, nextState)
}
複製程式碼
接著PureComponent在生命週期函式裡面寫了shallowCompare方法,shallowCompare裡面通過shallowEqual的返回值來返回ture還是false.
接著來看看shallowEqual函式,原始碼地址:
export default function shallEqual(objA, objB) {
// 從後面程式碼可以看出,對於兩個物件的比較為這裡的程式碼
if (objA === objB) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
複製程式碼
從上面淺比較的原始碼shallowEqual函式可以看出,shallEqual對於物件的比較僅僅通過if (objA === objB) { return true; }
來判斷,而let a = { list: { title: 'react line1' } }
,let b = { list: { title: 'react line1' } }
, a === b值為false,所以這就很好的解釋了上面PureComponent並沒有阻止不必要render的原因。
所以我們來改進程式碼,使得PureComponent的props如果傳入物件情況下應該如何起效:
在App.js裡面修改PureComponentList元件傳入item.title而不是item物件,瀏覽器只輸出兩次log:
this.state.data.map((item, index) => (
// <PureComponentList key={index} list={item}/>
<PureComponentList key={index} title={item.title}/>
))
複製程式碼
通過解構item物件,傳入item.title,這樣就可以進行淺比較,來達到優化不必要渲染的目的.
PureComponent原則
由上面探究PureComponent原始碼我們知道,PureComponent的元件在props或者state的屬性值是物件的情況下,並不能阻止不必要的渲染,是因為自動載入的shouldComponentUpdate裡面做的只是淺比較,所以想要用PureComponent的特性,應該遵守原則:
- 確保資料型別是值型別
- 如果是引用型別,不應當有深層次的資料變化(解構).
React.memo
在使用PureComponent的時候,只能把react元件寫成是class的形式,不能使用函式的形式;react v16.6.0之後,可以使用React.memo來實現函式式的元件,也有了PureComponent的功能。
List元件的PureComponent:
const ListComponent = React.memo(() => (
<div>{this.props.data || 'loading'}</div>
))
複製程式碼
注:上面涉及到的所有:point_right:原始碼