React學習-setState的執行機制


轉自:https://www.cnblogs.com/mengff/p/9611614.html

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM