做者 : 墨成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瀏覽器
(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 ,更能夠了解它的實現架構和設計原理,並運用到實際的項目中 .