起因
用React.PureComponent時,更新state裏面的count用的方式是++this.state.count,但是意外的導致組件沒有重新render(本人用Hook組件較多,所以感到很疑惑)
import React from 'react';
import { Button } from 'antd-mobile';
class DemoChildClass extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
num: 1
}
}
addNum = () => {
this.setState({
num: ++this.state.num
}, () => {
console.log('fffgggghhhh1111', this.state.num)
})
console.log('fffgggghhhh222', this.state.num)
}
render() {
const { num } = this.state;
return (
<div className='demo-child'>
這是兒子,這個數字就是{num},hhhh< br />
<Button onClick={this.addNum}>子++++</Button>
</div>
)
}
}
export default DemoChildClass;
打印結果如下:
一開始是沒有加兩個console.log的,所以按照慣識,認為代碼沒問題,邏輯沒問題,但是預期是錯誤的。
於是,更改一下setstate方式,如下:
addNum = () => {
let { num } = this.state;
this.setState({
num: ++num
})
}
發現他喵的render了?WHF???為什麼?
開始時,認為是PureComponent的問題,內部做了優化,導致渲染更新跟預期不一致,於是去看了PureComponent的源碼。
PureComponent
PureComponent並不是react與生俱來就有的,而應該是在15.3版本之後才出現的,主要是為了取代之前的PureRenderMixin。
PureComponent其實就是一個繼承自Component的子類,會自動加載shouldComponentUpdate函數。當組件需要更新的時候,shouldComponentUpdate會對組件的props和state進行一次淺比較。如果props和state都沒有發生變化,那麼render方法也就不會觸發,當然也就省去了之後的虛擬dom的生成和對比,在react性能方面得到了優化。
PureComponent源碼:
export defualut function PureComponent (props, context) {
Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototye)
PureComponent.prototype.contructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare (nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
我們可以看到PrueComponent總體來説就是繼承了Component,只不過是將shouldComponentUpdate重寫成了shallowCompare。而在shallowCompare中只是返回了shallowEqual的返回值。
shallowEqual的源碼:
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(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 (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
所以從shallowEqual中可以看出,其實就是比較了傳入的對象是不是一致,也就是淺比較了,props和state是不是一樣。從而來實現了一個另類的shouldComponentUpdate函數。所以從源碼來,PureCompoennt僅僅是一個props和state的淺比較。當props和state是對象的時候,並不能阻止不必要的渲染。
比較順序?
step1:先判斷兩個對象是否地址相同。如果地址相同,就直接返回true;如果地址不相同,就繼續判斷(基本類型的相等性判斷);
step2:再判斷兩個props的key數量,是否相同,如果相同就繼續下一步的判斷;如果不相同,就直接返回false;
step3:最後一步,分別判斷每個key對應的value值,是否相同。判斷value是否相同,使用的是object.is()。
附PureComponent和Component的區別是什麼?
1、就像是上面介紹PureComponent一樣,和Component的一個最大的區別在於PureComponent會自動執行shouldComponentUpdate函數,通過shallowEqual的淺對比,實現react的性能優化。而Component必須要通過自己去調用生命週期函數shouldComponentUpdate來實現react組件的優化;
2、PureComponent不僅會影響本身,而且會影響子組件,所以PureComponent最佳情況是展示組件
(1)加入父組件是繼承自PureComponent,而子組件是繼承自Component,那麼如果當父組件的props或者是state沒有變化而子組件的props或者state有變化,那麼此時子組件也不會有更新,因為子組件受到父組件的印象,父組件沒有更新。
(2)如果,父子組件均繼承自PureComponent,那麼父子組件的更新就會依賴與各自的props和state。
(3)父組件是繼承自Component,而子組件是繼承自PureComponent那麼就是看各自的props和state。
(4)當然如果父子組件都是繼承自Component那麼就是隻要有更新,那麼都會去重新渲染。
3、若是數組和對象等引用類型,則要引用不同,才會渲染;
4、如果prop和state每次都會變,那麼PureComponent的效率還不如Component,因為你知道的,進行淺比較也是需要時間;
5、若有shouldComponentUpdate,則執行它,若沒有這個方法會判斷是不是PureComponent,若是,進行淺比較。
上面一段屬於擴展了,我們回到正題,既然PureComponent進行了淺比較,那就説明preState的值和nextState的值是一樣的,所以沒有進行render,通過打印我們也可以看到:
那究竟是為什麼呢?
最終,我們在一位博客大佬的解釋下知道了:
this.setState({
num: ++this.state.num
})
就相當於下面這段代碼:
const newNum = ++this.state.num;
this.setState({
num: newNum
})
解釋一下,【++this.state.num】,去更改了state的值(也更改了preState),但是沒有用setState去更新,所以沒有render。然後再進行setState,這時PureComponent會進行淺比較,preState和nextState是一樣的,所以還是沒有進行render。
也就是説在PureComponent組件中,假如先進行數據更新,然後再進行setState的話,都會是一樣的結果,不會render。然鵝,在普通Component組件中,這些問題將不再存在。
所以,在PureComponent組件中,更新state最好的方式是更改state的引用地址,來觸發render,但是如果都是採用這種方式的話,就不要用PureComponent了,直接用Component吧!!
最後再來解釋一下下面這個寫法為啥沒問題:
addNum = () => {
let { num } = this.state;
this.setState({
num: ++num
})
}
解構或者重新定義一個值去接受num,相當於在棧內存中重新開闢了一塊內存空間變量叫num2,然後進行++num2,改的只是num2,state中num還是原來的值,所以在進行淺比較時,preState和nextState是不一樣的,所以會重新render。
完結~