setState那點事

首先引用個例子react

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

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);

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

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);
    }, 0);
  }

  render() {
    return null;
  }
};
複製代碼

以上代碼依次輸出結果是?不知道的結果的能夠接下來看 知道結果的能夠直接跳過 結果依次是0 0 2 3 下面剖析下setState應用場景git

第一個場景在聲明周期函數中的使用

......
  state = { val: 0 }
  componentDidMount() {
    this.setState({ 
      val: this.state.val + 1
    })
    console.log(this.state.val)  
  }
 ......
複製代碼

這裏的輸出值仍是初始值0 看下源碼 關於github

function enqueueUpdate(component) {
    ......
      if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
    
      dirtyComponents.push(component);
    ......
    }
複製代碼

看到有個if判斷 isBatchchingUpdates true & false 自行打個斷點能夠看到這componentDidMount時候已經將isBatchingUpdates設置了爲true 全部就會執行dirtyComponents.push(component); 不會立馬更新state 這時候打印就仍是初始值 0promise

這個if判斷的過程看似成了一個異步的操做bash

第二個場景在setTimeout

......
  state = { val: 0 }
  componentDidMount() {
   setTimeout( () => {
     this.setState({ 
       val: this.state.val + 1
     })
     console.log(this.state.val)
   })
  }
 ......
複製代碼

這裏打印的是1 斷點看到這時候沒有batchedUpdate調用 isBatchingUpdates仍是爲false 這樣batchingStrategy.batchedUpdates(enqueueUpdate, component); 這就話就當即生效了 全部這時候val 會當即更新 打印是1異步

第三個場景是批量更新

......
  state = { val: 0 }
  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
  }
 ......
複製代碼

這裏結果仍是最後一個1 有一個update隊列 對最後一個更新async

實現一個簡易的模擬異步setState方法

var stateQueue = [] //  state隊列
  function mysetState(state, componentstate) {
    
    // 寫一個異步的操做
    if ( stateQueue.length === 0 ) {
        promiseasync( cleanflush );  // 清空隊列
    }
    stateQueue.push({
        state,
        componentstate
    })
  }
  function cleanflush() {
    var item =setStateQueue.shift() 
    while(item) {
     const { state, componentstate } = item
     // 設置一個preState 保存以前的
     if(!componentstate.prevState) {
        componentstate.prevState = Object.assign( {}, componentstate.state);
     }
     if(typeof state === 'function') {  // 判斷是否爲回調
         Object.assign( componentstate.state, state( componentstate.prevState, componentstate.props ));
     } else {
        Object.assign(componentstate.state, state) // 合併state
     }
    }
  }
  function promiseasync(fn) {
    return Promise.resolve().then(function() {
      fn
    })
  }
複製代碼

總結

  • setState 內部並非異步代碼實現 只是函數調用棧順訊的問題
  • 鉤子函數中是異步的 setTimeout中是同步的
  • 在異步操做中對同一個值進行屢次重複setState 那將根據批量更新策略執行取最後一次的執行
  • 在同步操做中 將合併一塊更新
相關文章
相關標籤/搜索