基於React 源碼深刻淺出setState:setState異步實現

做者 : 墨成javascript

React 版本 :16.4.1java

React啓示錄裏咱們說setState是異步的,咱們在代碼中也展現了這種特性,那麼FB的工程師們是如何實現呢,本文將基於React的源碼進一步揭開這層面紗。react

在介紹以前咱們首先看下setState的實現和FB工程師的註釋,我簡單的做了一些翻譯程序員

//ReactBaseClass.js
/** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * //咱們應該使用這個方法(setState)來改變state,而不是使用this.state(翻譯可能跟原文有一點誤差,當表達的意思是這樣) * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. *//setState並不會當即更新,在調用這個方法後拿到的this.state可能仍是原來的值
 * There is no guarantee that calls to `setState` will run synchronously,
 * as they may eventually be batched together.  You can provide an optional
 * callback that will be executed when the call to setState is actually
 * completed.
 *//setState不能保證是同步的,他們有可能會批量處理。你能夠提供一個可選的回調函數來拿到更改以後的值
 * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. *//setState第一參數是一個function ,他會在將來的某個時間點執行。在執行這個functon時咱們都是拿到的最新的組件信息 *//(好比state,props, context).這些值根尼經過this.state的不同,由於function實在receiveProps以後在 *//shouldComponentDupdate以前,因此這些值還沒更新到當前this指向的這些值 * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};複製代碼

 上面的註釋告訴咱們:chrome

1.setState()第一參數是function也是異步的promise

2.function的執行週期是:receiveProps=>function=>shouldComponentUpdate瀏覽器

3. 基於第二點的說明,再回頭看看官網對function的寫法,咱們知道爲何這個函數的第一參數叫 preState ,而props 就叫props而不是叫preProps的緣由了.

(prevState, props) => stateChange複製代碼

若是關於第一參數function的說明還不是很理解,多看幾眼,多想一想React生命週期,那就會茅塞頓開 .bash

言歸正傳,再看看整段代碼架構

Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};複製代碼

參數:異步

@param  partialState 部分狀態複製代碼

這個參數或許是整個state,或許只是state其中的一部分 ,最終React合併(就是merge).

state = {name:'myName',age:18} => setState({age:16}/*state的一部分*/)=>state={name:'myName',age:16}複製代碼

其實就是Object.assign(源碼:Object.assign({}, prevState, partialState))的理解 .

@param  callback 回調函數,後面專門開一篇來說解複製代碼

invariant這段代碼主要對參數做了一些驗證 ,因此 setState()只接受三種類型的參數,好比 object,function和與null 恆等的,好比undefined ,false . 若是你使用這三種斷定類型以外的狀況,會優雅的提示你錯誤,好比下面這個代碼 :

this.setState(Symbol());複製代碼

錯誤信息以下:



this.updater.enqueueSetState(this, partialState, callback, 'setState');複製代碼

在這裏 調用了 this.updater中的enqueueSetState,看着名字就知道這是一個setState的隊列(準確的說它是一個鏈表),而這個 updater

// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;複製代碼

上面的註釋很清楚地告訴你 咱們有個默認的初始值,真正的值其實就在renderer的時候注入進來的,在下一篇文章中我會專門針對這個updater進入深刻理解,如今咱們來了解下這個updater的結構

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueReplaceState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ReplaceState;
    update.payload = payload;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'replaceState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueForceUpdate(inst, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'forceUpdate');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};複製代碼


總結一下:這個setState其實什麼都沒作,它只是簡單的把這個update操做裝入了一個"隊列"裏(看上去是這樣,不是嗎?).

那個問題來了,爲何它就是異步的呢?JavaScript不是單線程嗎 ?

先拋開React Fiber 架構不談,咱們來聊聊 Javascript的 Event Loop  ,你們能夠在網上找找關於 Event loop,在這裏我重點推薦是 Philip Roberts 在JSConf上關於Eevent loop深情並茂的介紹(YouTube)

那首先你們在網上看到的資料無怪乎把task queue分爲microtask和macrotask,同時他們也有個很好聽的名字:微任務和宏任務(實際上是在外語中他們只是用這個詞表示優先級),宏任務的優先級大於微任務。而後會把setTimeout,setInterval等等歸於 macrotask,把promise歸於mircotask.

在這裏我表示,他們的論點放在單個瀏覽器,好比說chrome是對的,可是你們想過沒有,對Promise的原生態支持是ES6(也有超前意識的瀏覽器廠商),實際狀況是每一個瀏覽器對他們的處理不同(每一個瀏覽器的程序員不同嘛),你們能夠把相同的代碼放在不一樣的瀏覽器,輸出的結果是不同的 


這裏你只要記住兩點 :

1.全部的native code回調(window對象上的code)都是 macrotask,好比setTimeout ,Promise,setInterval

2.native code的優先級要大於普通回調函數

React啓示錄裏我有說咱們能夠"同步"(這裏的同步不是說他直接在stack的調用方式,而是看上去同步拿到告終果)拿到state變化後的值,如今把咱們上一節部分代碼做一點修改:

changeValue=()=>{
   setTimeout(()=>{
       this.setState(
             {value:'I have a new value by setTimeout'}
       );
        console.log(this.state.value);
    },0)

    new Promise((resolve,reject)=>{
       resolve();
    }).then(()=>{
       this.setState(
            {value:'I have a new value by promise '}
        );
        console.log(this.state.value);
    });
};

//result:
I have a new  value by promise 
I have a new  value by setTimeout
複製代碼

這裏並無等待this.setState()隊列執行便可得到修改後的值,請務必在本身的代碼中執行,由於我說的多是錯的。


事實上,setState到這裏已經完成了使命,剩下的全部任務都都交給了這個updater,updater是何方神聖,又是如何工做,它與react 16提出的fiber有什麼樣的關係 ,reconciler又是什麼東西 ?

持續更新中......不要走開,全面瞭解 react的實現原理,不但能夠幫助你更好的使用和優化React ,更能夠了解它的實現架構和設計原理,並運用到實際的項目中 .

相關文章
相關標籤/搜索