React的setState執行機制

看風景就發表於2018-09-09

1. setState基本特點

1. setState是同步執行的

setState是同步執行的,但是state並不一定會同步更新

2. setState在React生命週期和合成事件中批量覆蓋執行

在React的生命週期鉤子和合成事件中,多次執行setState,會批量執行

具體表現為,多次同步執行的setState,會進行合併,類似於Object.assign,相同的key,後面的會覆蓋前面的

當遇到多個setState呼叫時候,會提取單次傳遞setState的物件,把他們合併在一起形成一個新的
單一物件,並用這個單一的物件去做setState的事情,就像Object.assign的物件合併,後一個
key值會覆蓋前面的key值

const a = {name : 'kong', age : '17'}
const b = {name : 'fang', sex : 'men'}
Object.assign({}, a, b);
//{name : 'fang', age : '17', sex : 'men'}

name被後面的覆蓋了,但是age和sex都起作用了

例如:

class Hello extends React.Component {
    constructor(){
      super();
      this.state = {
        name: 'aa'
    }
  }
  componentWillMount(){
      this.setState({
        name: 'aa' + 1
    });
    console.log(this.state.name); //aa
    this.setState({
        name: 'aa' + 1
    });
    console.log(this.state.name); //aa
  }
  render() {
    return <div>
      <div>Hello {this.props.name}</div>
      <div>Hello {this.state.name}</div>
    </div>;
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

componentWillMount中兩個log均為初始狀態aa,而render中的state.name則為aa2
componentWillMount中的setState均執行了,但是state的更新是延遲的,所以log出的state均為aa
而render中的state.name則在state更新之後,而且只有第二次的aa1起了作用

3. setState在原生事件,setTimeout,setInterval,Promise等非同步操作中,state會同步更新

非同步操作中setState,即使在React的鉤子或合成事件中,state都不會批量更新,而是會同步更新,
多次連續操作setState,每次都會re-render,state會同步更新

2. setState的形式

setState(object,[callback]) //物件式,object為nextState
setState(function,[callback]) //函式式,function為(prevState,props) => stateChange

[callback]則為state更新之後的回撥,此時state已經完成更新,可以取到更新後的state
[callback]是在setState之後,更準確來說是當正式執行batchUpdate佇列的state更新完成後就會執行,不是在re-rendered之後

使用兩種形式的setState,state的更新都是非同步的,但是多次連續使用函式式的setState,
React本身會進行一個遞迴傳遞呼叫,將上一次函式執行後的state傳給下一個函式,因此每次執行
setState後能讀取到更新後的state值。

如果物件式和函式式的setState混合使用,則物件式的會覆蓋前面無論函式式還是物件式的任何setState,
但是不會影響後面的setState。

例如:

function increment(state,props){
    return {count: state.count + 1};
}

function incrementMultiple(){
    this.setState(increment);
    this.setState(increment);
    this.setState({count: this.state.count + 1});
    this.setState(increment);
}

上面三個函式式的setState中間插入一個物件式的setState,則最後的結果是2,而不是4,
因為物件式的setState將前面的任何形式的setState覆蓋了,但是後面的setState依然起作用

3. setState的基本過程

setState的呼叫會引起React的更新生命週期的4個函式執行。

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate

當shouldComponentUpdate執行時,返回true,進行下一步,this.state沒有被更新
返回false,停止,更新this.state

當componentWillUpdate被呼叫時,this.state也沒有被更新

直到render被呼叫時候,this.state才被更新。

總之,直到下一次render函式呼叫(或者下一次shouldComponentUpdate返回false時)才能得到更新後的this.state
因此獲取更新後的狀態可以有3種方法:

1. setState函式式

2. setState在setTimeout,Promise等非同步中執行

setStatePromise(updator) {
    return new Promise(((resolve, reject) => {
        this.setState(updator, resolve);
    }));
}

componentWillMount() {
    this.setStatePromise(({ num }) => ({
        num: num + 1,
    })).then(() => {
        console.log(this.state.num);
    });
}

或者

function setStateAsync(nextState){  
  return new Promise(resolve => {
    this.setState(nextState, resolve);
  });
}

async func() {  
  ...
  await this.setStateAsync({count: this.state.count + 1});
  await this.setStateAsync({count: this.state.count + 1});
}

3. setState callback

setState({
    index: 1
}}, function(){
    console.log(this.state.index);
})

4. componentDidUpdate

componentDidUpdate(){
    console.log(this.state.index);
}

4. setState批量更新的過程

在React的生命週期和合成事件執行前後都有相應的鉤子,分別是pre鉤子和post鉤子,pre鉤子會呼叫batchedUpdate方法將isBatchingUpdates變數置為true,開啟批量更新,而post鉤子會將isBatchingUpdates置為false

如下圖所示:

isBatchingUpdates變數置為true,則會走批量更新分支,setState的更新會被存入佇列中,待同步程式碼執行完後,再執行佇列中的state更新。

而在原生事件和非同步操作中,不會執行pre鉤子,或者生命週期的中的非同步操作之前執行了pre鉤子,但是pos鉤子也在非同步操作之前執行完了,isBatchingUpdates必定為false,也就不會進行批量更新。

5. setState的缺點

1. setState有可能迴圈呼叫

呼叫setState之後,shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate 等生命週期函式會依次被呼叫(如果shouldComponentUpdate沒有返回 false的話),如果我們在render、componentWillUpdate或componentDidUpdate中呼叫了setState方法,那麼可能會造成迴圈呼叫,最終導致瀏覽器記憶體佔滿後崩潰

2、setState可能會引發不必要的渲染

可能造成不必要渲染的因素如下:
(1)新 state 和之前的一樣。這種情況可以通過 shouldComponentUpdate 解決。
(2)state 中的某些屬性和檢視沒有關係(譬如事件、timer ID等),這些屬性改變不影響檢視的顯示。

3、setState並不總能有效地管理元件中的所有狀態

因為元件中的某些屬性是和檢視沒有關係的,當元件變得複雜的時候可能會出現各種各樣的狀態需要管理,這時候用setState管理所有狀態是不可取的。state中本應該只儲存與渲染有關的狀態,而與渲染無關的狀態儘量不放在state中管理,可以直接儲存為元件例項的屬性,這樣在屬性改變的時候,不會觸發渲染,避免浪費

6. setState和replaceState的區別

setState是修改其中的部分狀態,相當於Object.assign,只是覆蓋,不會減少原來的狀態
replaceState是完全替換原來的狀態,相當於賦值,將原來的state替換為另一個物件,如果新狀態屬性減少,那麼state中就沒有這個狀態了

7. 一個例項分析

上圖的執行結果為 0 0 1 1 3 4

 

 

參考: http://www.360doc.com/content/17/0803/18/27576111_676420051.shtml
    https://blog.csdn.net/kongjunchao159/article/details/72626637
    https://blog.csdn.net/michellezhai/article/details/80098211
    https://www.cnblogs.com/danceonbeat/p/6993674.html
    https://segmentfault.com/a/1190000010682761
    https://segmentfault.com/a/1190000015821018

相關文章