react 常見setState的原理解析

流氓流年流浪發表於2018-11-21

React.setState

首先引入一個栗子

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }
  render() {
    return null;
  }
};
複製程式碼

4次log的值 分別為 0 0 2 3

setState 幹了什麼

image

說一下批量更新

image

解讀為什麼直接修改this.state無效

要知道setState本質是通過一個佇列機制實現state更新的。 執行setState時,會將需要更新的state合併後放入狀態佇列,而不會立刻更新state,佇列機制可以批量更新state。 如果不通過setState而直接修改this.state,那麼這個state不會放入狀態佇列中,下次呼叫setState時對狀態佇列進行合併時,會忽略之前直接被修改的state,這樣我們就無法合併了,而且實際也沒有把你想要的state更新上去。

什麼是批量更新 Batch Update

在一些mv*框架中,,就是將一段時間內對model的修改批量更新到view的機制。比如那前端比較火的React、vue(nextTick機制,檢視的更新以及實現)為例。

vue的nextTick機制 www.cnblogs.com/hity-tt/p/6…

html5新特性變動觀察器 www.cnblogs.com/jscode/p/36…

訊息程式 www.ruanyifeng.com/blog/2013/1…

vue的批量更新體現

  1. Mutation Observer(變動觀察器)是監視DOM變動的介面。當DOM物件樹發生任何變動時,Mutation Observer會得到通知。
  2. 概念上,它很接近事件。可以理解為,當DOM發生變動會觸發Mutation Observer事件。但是,它與事件有一個本質不同:事件是同步觸發,也就是說DOM發生變動立刻會觸發相應的事件;
  3. Mutation Observer則是非同步觸發,DOM發生變動以後,並不會馬上觸發,而是要等到當前所有DOM操作都結束後才觸發。
  4. 這樣設計是為了應付DOM變動頻繁的情況。舉例來說,如果在文件中連續插入1000個段落(p元素),會連續觸發1000個插入事件,執行每個事件的回撥函式,這很可能造成瀏覽器的卡頓;
  5. 而Mutation Observer完全不同,只在1000個段落都插入結束後才會觸發,而且只觸發一次。

setState之後發生的事情

  • 在官方的描述中,setState操作並不保證是同步的,也可以認為是非同步的。
  • React在setState之後,會經對state進行diff,判斷是否有改變,然後去diff dom決定是否要更新UI。如果這一系列過程立刻發生在每一個setState之後,就可能會有效能問題。
  • 在短時間內頻繁setState。React會將state的改變壓入棧中,在合適的時機,批量更新state和檢視,達到提高效能的效果。

總結

  1. 通過setState去更新this.state,不要直接操作this.state,請把它當成不可變的。
  2. 呼叫setState更新this.state不是馬上生效的,它是非同步滴,所以不要天真以為執行完setState後this.state就是最新的值了。
  3. 多個順序執行的setState不是同步地一個一個執行滴,會一個一個加入佇列,然後最後一起執行,即批處理

如何知道state已經被更新

傳入回撥函式

setState({
    index: 1
}}, function(){
    console.log(this.state.index);
})
複製程式碼

在鉤子函式中體現

componentDidUpdate(){
    console.log(this.state.index);
}
複製程式碼

setState的另外一種方式 (需要使用上一次的state的值)

在setState的第一個引數中傳入function,該function會被壓入呼叫棧中,在state真正改變後,按順序回撥棧裡面的function。該function的第一個引數為上一次更新後的state。這樣就能確保你下一次的操作拿到的是你預期的值

lass Com extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            index: 0
        }
        this.add = this.add.bind(this);
    }

    add(){
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
    }
}
複製程式碼

注意點

  1. setState可能會引發不必要的渲染(renders)
  2. setState無法完全掌控應用中所有元件的狀態

相關文章