Stories

Detail Return Return

React.PureComponent深入瞭解機制及其原理 - Stories Detail

起因

用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;

打印結果如下:
image.png

一開始是沒有加兩個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,通過打印我們也可以看到:

image.png

那究竟是為什麼呢?
最終,我們在一位博客大佬的解釋下知道了:

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。

完結~

user avatar Leesz Avatar linlinma Avatar yinzhixiaxue Avatar kobe_fans_zxc Avatar aqiongbei Avatar razyliang Avatar leexiaohui1997 Avatar banana_god Avatar u_17443142 Avatar zero_dev Avatar solvep Avatar woniuseo Avatar
Favorites 88 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.