【讀懂源碼】React 架構(gòu)的演變 - 從同步到異步
寫這篇文章的目的,主要是想弄懂 React 最新的 fiber 架構(gòu)到底是什么東西,但是看了網(wǎng)上的很多文章,要不模棱兩可,要不就是一頓復(fù)制粘貼,根本看不懂,于是開始認(rèn)真鉆研源碼。鉆研過程中,發(fā)現(xiàn)我想得太簡單了,React 源碼的復(fù)雜程度遠(yuǎn)超我的想象,于是打算分幾個模塊了剖析,今天先講一講 React 的更新策略從同步變?yōu)楫惒降难葑冞^程。
從 setState 說起
React 16 之所以要進(jìn)行一次大重構(gòu),是因?yàn)?React 之前的版本有一些不可避免的缺陷,一些更新操作,需要由同步改成異步。所以我們先聊聊 React 15 是如何進(jìn)行一次 setState 的。
import?React?from?'react';
class?App?extends?React.Component?{
??state?=?{?val:?0?}
??componentDidMount()?{
????//?第一次調(diào)用
????this.setState({?val:?this.state.val?+?1?});
????console.log('first?setState',?this.state);
????//?第二次調(diào)用
????this.setState({?val:?this.state.val?+?1?});
????console.log('second?setState',?this.state);
????//?第三次調(diào)用
????this.setState({?val:?this.state.val?+?1?},?()?=>?{
??????console.log('in?callback',?this.state)
????});
??}
??render()?{
????return?<div>?val:?{?this.state.val?}?div>
??}
}
export?default?App;
熟悉 React 的同學(xué)應(yīng)該知道,在 React 的生命周期內(nèi),多次 setState 會被合并成一次,這里雖然連續(xù)進(jìn)行了三次 setState,state.val 的值實(shí)際上只重新計算了一次。

每次 setState 之后,立即獲取 state 會發(fā)現(xiàn)并沒有更新,只有在 setState 的回調(diào)函數(shù)內(nèi)才能拿到最新的結(jié)果,這點(diǎn)通過我們在控制臺輸出的結(jié)果就可以證實(shí)。

網(wǎng)上有很多文章稱 setState 是『異步操作』,所以導(dǎo)致 setState 之后并不能獲取到最新值,其實(shí)這個觀點(diǎn)是錯誤的。setState 是一次同步操作,只是每次操作之后并沒有立即執(zhí)行,而是將 setState 進(jìn)行了緩存,mount 流程結(jié)束或事件操作結(jié)束,才會拿出所有的 state 進(jìn)行一次計算。如果 setState 脫離了 React 的生命周期或者 React 提供的事件流,setState 之后就能立即拿到結(jié)果。
我們修改上面的代碼,將 setState 放入 setTimeout 中,在下一個任務(wù)隊(duì)列進(jìn)行執(zhí)行。
import?React?from?'react';
class?App?extends?React.Component?{
??state?=?{?val:?0?}
??componentDidMount()?{
????setTimeout(()?=>?{
??????//?第一次調(diào)用
??????this.setState({?val:?this.state.val?+?1?});
??????console.log('first?setState',?this.state);
??
??????//?第二次調(diào)用
??????this.setState({?val:?this.state.val?+?1?});
??????console.log('second?setState',?this.state);
????});
??}
??render()?{
????return?<div>?val:?{?this.state.val?}?div>
??}
}
export?default?App;
可以看到,setState 之后就能立即看到state.val 的值發(fā)生了變化。

為了更加深入理解 setState,下面簡單講解一下React 15 中 setState 的更新邏輯,下面的代碼是對源碼的一些精簡,并非完整邏輯。
舊版本 setState 源碼分析
setState 的主要邏輯都在 ReactUpdateQueue 中實(shí)現(xiàn),在調(diào)用 setState 后,并沒有立即修改 state,而是將傳入的參數(shù)放到了組件內(nèi)部的 _pendingStateQueue 中,之后調(diào)用 enqueueUpdate 來進(jìn)行更新。
//?對外暴露的?React.Component
function?ReactComponent()?{
??this.updater?=?ReactUpdateQueue;
}
//?setState?方法掛載到原型鏈上
ReactComponent.prototype.setState?=?function?(partialState,?callback)?{
??//?調(diào)用?setState?后,會調(diào)用內(nèi)部的?updater.enqueueSetState
??this.updater.enqueueSetState(this,?partialState);
??if?(callback)?{
????this.updater.enqueueCallback(this,?callback,?'setState');
??}
};
var?ReactUpdateQueue?=?{
??enqueueSetState(component,?partialState)?{
????//?在組件的?_pendingStateQueue?上暫存新的?state
????if?(!component._pendingStateQueue)?{
??????component._pendingStateQueue?=?[];
????}
????var?queue?=?component._pendingStateQueue;
????queue.push(partialState);
????enqueueUpdate(component);
??},
??enqueueCallback:?function?(component,?callback,?callerName)?{
????//?在組件的?_pendingCallbacks?上暫存?callback
????if?(component._pendingCallbacks)?{
??????component._pendingCallbacks.push(callback);
????}?else?{
??????component._pendingCallbacks?=?[callback];
????}
????enqueueUpdate(component);
??}
}
enqueueUpdate 首先會通過 batchingStrategy.isBatchingUpdates 判斷當(dāng)前是否在更新流程,如果不在更新流程,會調(diào)用 batchingStrategy.batchedUpdates() 進(jìn)行更新。如果在流程中,會將待更新的組件放入 dirtyComponents 進(jìn)行緩存。
var?dirtyComponents?=?[];
function?enqueueUpdate(component)?{
??if?(!batchingStrategy.isBatchingUpdates)?{
???//?開始進(jìn)行批量更新
????batchingStrategy.batchedUpdates(enqueueUpdate,?component);
????return;
??}
??//?如果在更新流程,則將組件放入臟組件隊(duì)列,表示組件待更新
??dirtyComponents.push(component);
}
batchingStrategy 是 React 進(jìn)行批處理的一種策略,該策略的實(shí)現(xiàn)基于 Transaction,雖然名字和數(shù)據(jù)庫的事務(wù)一樣,但是做的事情卻不一樣。
class?ReactDefaultBatchingStrategyTransaction?extends?Transaction?{
??constructor()?{
????this.reinitializeTransaction()
??}
??getTransactionWrappers?()?{
????return?[
??????{
????????initialize:?()?=>?{},
????????close:?ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
??????},
??????{
????????initialize:?()?=>?{},
????????close:?()?=>?{
??????????ReactDefaultBatchingStrategy.isBatchingUpdates?=?false;
????????}
??????}
????]
??}
}
var?transaction?=?new?ReactDefaultBatchingStrategyTransaction();
var?batchingStrategy?=?{
??//?判斷是否在更新流程中
??isBatchingUpdates:?false,
??//?開始進(jìn)行批量更新
??batchedUpdates:?function?(callback,?component)?{
????//?獲取之前的更新狀態(tài)
????var?alreadyBatchingUpdates?=?ReactDefaultBatchingStrategy.isBatchingUpdates;
??//?將更新狀態(tài)修改為?true
????ReactDefaultBatchingStrategy.isBatchingUpdates?=?true;
????if?(alreadyBatchingUpdates)?{
??????//?如果已經(jīng)在更新狀態(tài)中,等待之前的更新結(jié)束
??????return?callback(callback,?component);
????}?else?{
??????//?進(jìn)行更新
??????return?transaction.perform(callback,?null,?component);
????}
??}
};
Transaction 通過 perform 方法啟動,然后通過擴(kuò)展的 getTransactionWrappers 獲取一個數(shù)組,該數(shù)組內(nèi)存在多個 wrapper 對象,每個對象包含兩個屬性:initialize、close。perform 中會先調(diào)用所有的 wrapper.initialize,然后調(diào)用傳入的回調(diào),最后調(diào)用所有的 wrapper.close。
class?Transaction?{
?reinitializeTransaction()?{
????this.transactionWrappers?=?this.getTransactionWrappers();
??}
?perform(method,?scope,?...param)?{
????this.initializeAll(0);
????var?ret?=?method.call(scope,?...param);
????this.closeAll(0);
????return?ret;
??}
?initializeAll(startIndex)?{
????var?transactionWrappers?=?this.transactionWrappers;
????for?(var?i?=?startIndex;?i???????var?wrapper?=?transactionWrappers[i];
??????wrapper.initialize.call(this);
????}
??}
?closeAll(startIndex)?{
????var?transactionWrappers?=?this.transactionWrappers;
????for?(var?i?=?startIndex;?i???????var?wrapper?=?transactionWrappers[i];
??????wrapper.close.call(this);
????}
??}
}

React 源代碼的注釋中,也形象的展示了這一過程。
/*
*???????????????????????wrappers?(injected?at?creation?time)
*??????????????????????????????????????+????????+
*??????????????????????????????????????|????????|
*????????????????????+-----------------|--------|--------------+
*????????????????????|?????????????????v????????|??????????????|
*????????????????????|??????+---------------+???|??????????????|
*????????????????????|???+--|????wrapper1???|---|----+?????????|
*????????????????????|???|??+---------------+???v????|?????????|
*????????????????????|???|??????????+-------------+??|?????????|
*????????????????????|???|?????+----|???wrapper2??|--------+???|
*????????????????????|???|?????|????+-------------+??|?????|???|
*????????????????????|???|?????|?????????????????????|?????|???|
*????????????????????|???v?????v?????????????????????v?????v???|?wrapper
*????????????????????|?+---+?+---+???+---------+???+---+?+---+?|?invariants
*?perform(anyMethod)?|?|???|?|???|???|?????????|???|???|?|???|?|?maintained
*?+----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
*????????????????????|?|???|?|???|???|?????????|???|???|?|???|?|
*????????????????????|?|???|?|???|???|?????????|???|???|?|???|?|
*????????????????????|?|???|?|???|???|?????????|???|???|?|???|?|
*????????????????????|?+---+?+---+???+---------+???+---+?+---+?|
*????????????????????|??initialize????????????????????close????|
*????????????????????+-----------------------------------------+
*/
我們簡化一下代碼,再重新看一下 setState 的流程。
//?1.?調(diào)用?Component.setState
ReactComponent.prototype.setState?=?function?(partialState)?{
??this.updater.enqueueSetState(this,?partialState);
};
//?2.?調(diào)用?ReactUpdateQueue.enqueueSetState,將?state?值放到?_pendingStateQueue?進(jìn)行緩存
var?ReactUpdateQueue?=?{
??enqueueSetState(component,?partialState)?{
????var?queue?=?component._pendingStateQueue?||?(component._pendingStateQueue?=?[]);
????queue.push(partialState);
????enqueueUpdate(component);
??}
}
//?3.?判斷是否在更新過程中,如果不在就進(jìn)行更新
var?dirtyComponents?=?[];
function?enqueueUpdate(component)?{
??//?如果之前沒有更新,此時的?isBatchingUpdates?肯定是?false
??if?(!batchingStrategy.isBatchingUpdates)?{
????//?調(diào)用?batchingStrategy.batchedUpdates?進(jìn)行更新
????batchingStrategy.batchedUpdates(enqueueUpdate,?component);
????return;
??}
??dirtyComponents.push(component);
}
//?4.?進(jìn)行更新,更新邏輯放入事務(wù)中進(jìn)行處理
var?batchingStrategy?=?{
??isBatchingUpdates:?false,
??//?注意:此時的 callback 為 enqueueUpdate
??batchedUpdates:?function?(callback,?component)?{
????var?alreadyBatchingUpdates?=?ReactDefaultBatchingStrategy.isBatchingUpdates;
????ReactDefaultBatchingStrategy.isBatchingUpdates?=?true;
????if?(alreadyBatchingUpdates)?{
??????//?如果已經(jīng)在更新狀態(tài)中,重新調(diào)用?enqueueUpdate,將?component?放入?dirtyComponents
??????return?callback(callback,?component);
????}?else?{
??????//?進(jìn)行事務(wù)操作
??????return?transaction.perform(callback,?null,?component);
????}
??}
};
啟動事務(wù)可以拆分成三步來看:
先執(zhí)行 wrapper 的 initialize,此時的 initialize 都是一些空函數(shù),可以直接跳過; 然后執(zhí)行 callback(也就是 enqueueUpdate),執(zhí)行 enqueueUpdate 時,由于已經(jīng)進(jìn)入了更新狀態(tài), batchingStrategy.isBatchingUpdates被修改成了true,所以最后還是會把 component 放入臟組件隊(duì)列,等待更新;后面執(zhí)行的兩個 close 方法,第一個方法的 flushBatchedUpdates是用來進(jìn)行組件更新的,第二個方法用來修改更新狀態(tài),表示更新已經(jīng)結(jié)束。
getTransactionWrappers?()?{
??return?[
????{
??????initialize:?()?=>?{},
??????close:?ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
????},
????{
??????initialize:?()?=>?{},
??????close:?()?=>?{
????????ReactDefaultBatchingStrategy.isBatchingUpdates?=?false;
??????}
????}
??]
}
flushBatchedUpdates 里面會取出所有的臟組件隊(duì)列進(jìn)行 diff,最后更新到 DOM。
function?flushBatchedUpdates()?{
??if?(dirtyComponents.length)?{
????runBatchedUpdates()
??}
};
function?runBatchedUpdates()?{
??//?省略了一些去重和排序的操作
??for?(var?i?=?0;?i?????var?component?=?dirtyComponents[i];
????//?判斷組件是否需要更新,然后進(jìn)行 diff 操作,最后更新 DOM。
????ReactReconciler.performUpdateIfNecessary(component);
??}
}
performUpdateIfNecessary() 會調(diào)用 Component.updateComponent(),在 updateComponent() 中,會從 _pendingStateQueue 中取出所有的值來更新。
//?獲取最新的?state
_processPendingState()?{
??var?inst?=?this._instance;
??var?queue?=?this._pendingStateQueue;
??var?nextState?=?{?...inst.state?};
??for?(var?i?=?0;?i?????var?partial?=?queue[i];
????Object.assign(
??????nextState,
??????typeof?partial?===?'function'???partial(inst,?nextState)?:?partial
???);
??}
??return?nextState;
}
//?更新組件
updateComponent(prevParentElement,?nextParentElement)?{
??var?inst?=?this._instance;
??var?prevProps?=?prevParentElement.props;
??var?nextProps?=?nextParentElement.props;
??var?nextState?=?this._processPendingState();
??var?shouldUpdate?=?
??????!shallowEqual(prevProps,?nextProps)?||
??????!shallowEqual(inst.state,?nextState);
??
??if?(shouldUpdate)?{
????//?diff?、update?DOM
??}?else?{
????inst.props?=?nextProps;
????inst.state?=?nextState;
??}
??//?后續(xù)的操作包括判斷組件是否需要更新、diff、更新到?DOM
}
setState 合并原因
按照剛剛講解的邏輯,setState 的時候,batchingStrategy.isBatchingUpdates 為 false 會開啟一個事務(wù),將組件放入臟組件隊(duì)列,最后進(jìn)行更新操作,而且這里都是同步操作。講道理,setState 之后,我們可以立即拿到最新的 state。
然而,事實(shí)并非如此,在 React 的生命周期及其事件流中,batchingStrategy.isBatchingUpdates 的值早就被修改成了 true??梢钥纯聪旅鎯蓮垐D:


在組件 mount 和事件調(diào)用的時候,都會調(diào)用 batchedUpdates,這個時候已經(jīng)開始了事務(wù),所以只要不脫離 React,不管多少次 setState 都會把其組件放入臟組件隊(duì)列等待更新。一旦脫離 React 的管理,比如在 setTimeout 中,setState 立馬變成單打獨(dú)斗。
Concurrent 模式
React 16 引入的 Fiber 架構(gòu),就是為了后續(xù)的異步渲染能力做鋪墊,雖然架構(gòu)已經(jīng)切換,但是異步渲染的能力并沒有正式上線,我們只能在實(shí)驗(yàn)版中使用。異步渲染指的是 Concurrent 模式,下面是官網(wǎng)的介紹:
“Concurrent 模式是 React 的新功能,可幫助應(yīng)用保持響應(yīng),并根據(jù)用戶的設(shè)備性能和網(wǎng)速進(jìn)行適當(dāng)?shù)恼{(diào)整。

除了 Concurrent 模式,React 還提供了另外兩個模式, Legacy 模式依舊是同步更新的方式,可以認(rèn)為和舊版本保持一致的兼容模式,而 Blocking 模式是一個過渡版本。

Concurrent 模式說白就是讓組件更新異步化,切分時間片,渲染之前的調(diào)度、diff、更新都只在指定時間片進(jìn)行,如果超時就暫停放到下個時間片進(jìn)行,中途給瀏覽器一個喘息的時間。
“瀏覽器是單線程,它將 GUI 描繪,時間器處理,事件處理,JS 執(zhí)行,遠(yuǎn)程資源加載統(tǒng)統(tǒng)放在一起。當(dāng)做某件事,只有將它做完才能做下一件事。如果有足夠的時間,瀏覽器是會對我們的代碼進(jìn)行編譯優(yōu)化(JIT)及進(jìn)行熱代碼優(yōu)化,一些 DOM 操作,內(nèi)部也會對 reflow 進(jìn)行處理。reflow 是一個性能黑洞,很可能讓頁面的大多數(shù)元素進(jìn)行重新布局。
瀏覽器的運(yùn)作流程:
渲染 -> tasks -> 渲染 -> tasks -> 渲染 -> ....這些 tasks 中有些我們可控,有些不可控,比如 setTimeout 什么時候執(zhí)行不好說,它總是不準(zhǔn)時;資源加載時間不可控。但一些JS我們可以控制,讓它們分派執(zhí)行,tasks的時長不宜過長,這樣瀏覽器就有時間優(yōu)化 JS 代碼與修正 reflow !
總結(jié)一句,就是讓瀏覽器休息好,瀏覽器就能跑得更快。
-- by 司徒正美 《React Fiber架構(gòu)》

這里有個 demo,上面是一個?圍繞??運(yùn)轉(zhuǎn)的動畫,下面是 React 定時 setState 更新視圖,同步模式下,每次 setState 都會造成上面的動畫卡頓,而異步模式下的動畫就很流暢。
同步模式:

異步模式:

如何使用
雖然很多文章都在介紹 Concurrent 模式,但是這個能力并沒有真正上線,想要使用只能安裝實(shí)驗(yàn)版本。也可以直接通過這個 cdn :https://unpkg.com/browse/[email protected]/ 。
npm?install?react@experimental?react-dom@experimental
如果要開啟 Concurrent 模式,不能使用之前的 ReactDOM.render,需要替換成 ReactDOM.createRoot,而在實(shí)驗(yàn)版本中,由于 API 不夠穩(wěn)定, 需要通過 ReactDOM.unstable_createRoot 來啟用 Concurrent 模式。
import?ReactDOM?from?'react-dom';
import?App?from?'./App';
ReactDOM.unstable_createRoot(
??document.getElementById('root')
).render(<App?/>);
setState 合并更新
還記得之前 React15 的案例中,setTimeout 中進(jìn)行 setState ,state.val 的值會立即發(fā)生變化。同樣的代碼,我們拿到 Concurrent 模式下運(yùn)行一次。
import?React?from?'react';
class?App?extends?React.Component?{
??state?=?{?val:?0?}
??componentDidMount()?{
????setTimeout(()?=>?{
??????//?第一次調(diào)用
??????this.setState({?val:?this.state.val?+?1?});
??????console.log('first?setState',?this.state);
??
??????//?第二次調(diào)用
??????this.setState({?val:?this.state.val?+?1?});
??????console.log('second?setState',?this.state);
??????
??????this.setState({?val:?this.state.val?+?1?},?()?=>?{
????????console.log(this.state);
??????});
????});
??}
??render()?{
????return?<div>?val:?{?this.state.val?}?div>
??}
}
export?default?App;

說明在 Concurrent 模式下,即使脫離了 React 的生命周期,setState 依舊能夠合并更新。主要原因是 Concurrent 模式下,真正的更新操作被移到了下一個事件隊(duì)列中,類似于 Vue 的 nextTick。
更新機(jī)制變更
我們修改一下 demo,然后看下點(diǎn)擊按鈕之后的調(diào)用棧。
import?React?from?'react';
class?App?extends?React.Component?{
??state?=?{?val:?0?}
??clickBtn()?{
????this.setState({?val:?this.state.val?+?1?});
??}
??render()?{
????return?(<div>
??????<button?onClick={()?=>?{this.clickBtn()}}>click?addbutton>
??????<div>val:?{?this.state.val?}div>
????div>)
??}
}
export?default?App;


onClick 觸發(fā)后,進(jìn)行 setState 操作,然后調(diào)用 enquueState 方法,到這里看起來好像和之前的模式一樣,但是后面的操作基本都變了,因?yàn)?React 16 中已經(jīng)沒有了事務(wù)一說。
Component.setState()?=>?enquueState()?=>?scheduleUpdate()?=>?scheduleCallback()
=>?requestHostCallback(flushWork)?=>?postMessage()
真正的異步化邏輯就在 requestHostCallback、postMessage 里面,這是 React 內(nèi)部自己實(shí)現(xiàn)的一個調(diào)度器:https://github.com/facebook/react/blob/v16.13.1/packages/scheduler/index.js。
function?unstable_scheduleCallback(priorityLevel,?calback)?{
??var?currentTime?=?getCurrentTime();
??var?startTime?=?currentTime?+?delay;
??var?newTask?=?{
????id:?taskIdCounter++,
????startTime:?startTime,???????????//?任務(wù)開始時間
????expirationTime:?expirationTime,?//?任務(wù)終止時間
????priorityLevel:?priorityLevel,???//?調(diào)度優(yōu)先級
????callback:?callback,?????????????//?回調(diào)函數(shù)
??};
??if?(startTime?>?currentTime)?{
????//?超時處理,將任務(wù)放到?taskQueue,下一個時間片執(zhí)行
????//?源碼中其實(shí)是?timerQueue,后續(xù)會有個操作將?timerQueue?的?task?轉(zhuǎn)移到?taskQueue
???push(taskQueue,?newTask)
??}?else?{
????requestHostCallback(flushWork);
??}
??return?newTask;
}
requestHostCallback 的實(shí)現(xiàn)依賴于 MessageChannel,但是 MessageChannel 在這里并不是做消息通信用的,而是利用它的異步能力,給瀏覽器一個喘息的機(jī)會。說起 MessageChannel,Vue 2.5 的 nextTick 也有使用,但是 2.6 發(fā)布時又取消了。

MessageChannel 會暴露兩個對象,port1 和 port2,port1 發(fā)送的消息能被 port2 接收,同樣 port2 發(fā)送的消息也能被 port1 接收,只是接收消息的時機(jī)會放到下一個 macroTask 中。
var?{?port1,?port2?}?=?new?MessageChannel();
//?port1?接收?port2?的消息
port1.onmessage?=?function?(msg)?{?console.log('MessageChannel?exec')?}
//?port2?發(fā)送消息
port2.postMessage(null)
new?Promise(r?=>?r()).then(()?=>?console.log('promise?exec'))
setTimeout(()?=>?console.log('setTimeout?exec'))
console.log('start?run')

可以看到,port1 接收消息的時機(jī)比 Promise 所在的 microTask 要晚,但是早于 setTimeout。React 利用這個能力,給了瀏覽器一個喘息的時間,不至于被餓死。
還是之前的案例,同步更新時沒有給瀏覽器任何喘息,造成視圖的卡頓。

異步更新時,拆分了時間片,給了瀏覽器充分的時間更新動畫。

還是回到代碼層面,看看 React 是如何利用 MessageChannel 的。
var?isMessageLoopRunning?=?false;?//?更新狀態(tài)
var?scheduledHostCallback?=?null;?//?全局的回調(diào)
var?channel?=?new?MessageChannel();
var?port?=?channel.port2;
channel.port1.onmessage?=?function?()?{
??if?(scheduledHostCallback?!==?null)?{
????var?currentTime?=?getCurrentTime();
????//?重置超時時間
????deadline?=?currentTime?+?yieldInterval;
????var?hasTimeRemaining?=?true;
????//?執(zhí)行?callback
????var?hasMoreWork?=?scheduledHostCallback(hasTimeRemaining,?currentTime);
????if?(!hasMoreWork)?{
??????//?已經(jīng)沒有任務(wù)了,修改狀態(tài)
??????isMessageLoopRunning?=?false;
??????scheduledHostCallback?=?null;
????}?else?{
??????//?還有任務(wù),放到下個任務(wù)隊(duì)列執(zhí)行,給瀏覽器喘息的機(jī)會
??????port.postMessage(null);
????}
??}?else?{
????isMessageLoopRunning?=?false;
??}
};
requestHostCallback?=?function?(callback)?{
??//?callback?掛載到?scheduledHostCallback
??scheduledHostCallback?=?callback;
??if?(!isMessageLoopRunning)?{
????isMessageLoopRunning?=?true;
????//?推送消息,下個隊(duì)列隊(duì)列調(diào)用?callback
????port.postMessage(null);
??}
};
再看看之前傳入的 callback(flushWork),調(diào)用 workLoop,取出 taskQueue 中的任務(wù)執(zhí)行。
//?精簡了相當(dāng)多的代碼
function?flushWork(hasTimeRemaining,?initialTime)?{
??return?workLoop(hasTimeRemaining,?initialTime);
}
function?workLoop(hasTimeRemaining,?initialTime)?{
??var?currentTime?=?initialTime;
??//?scheduleCallback?進(jìn)行了?taskQueue?的?push?操作
??//?這里是獲取之前時間片未執(zhí)行的操作
??currentTask?=?peek(taskQueue);
??while?(currentTask?!==?null)?{
????if?(currentTask.expirationTime?>?currentTime)?{
??????//?超時需要中斷任務(wù)
??????break;
????}
????currentTask.callback();?????????//?執(zhí)行任務(wù)回調(diào)
????currentTime?=?getCurrentTime();?//?重置當(dāng)前時間
????currentTask?=?peek(taskQueue);??//?獲取新的任務(wù)
??}
?//?如果當(dāng)前任務(wù)不為空,表明是超時中斷,返回?true
??if?(currentTask?!==?null)?{
????return?true;
??}?else?{
????return?false;
??}
}
可以看出,React 通過 expirationTime 來判斷是否超時,如果超時就把任務(wù)放到后面來執(zhí)行。所以,異步模型中 setTimeout 里面進(jìn)行 setState,只要當(dāng)前時間片沒有結(jié)束(currentTime 小于 expirationTime),依舊可以將多個 setState 合并成一個。
接下來我們再做一個實(shí)驗(yàn),在 setTimeout 中連續(xù)進(jìn)行 500 次的 setState,看看最后生效的次數(shù)。
import?React?from?'react';
class?App?extends?React.Component?{
??state?=?{?val:?0?}
??clickBtn()?{
????for?(let?i?=?0;?i?500;?i++)?{
??????setTimeout(()?=>?{
????????this.setState({?val:?this.state.val?+?1?});
??????})
????}
??}
??render()?{
????return?(<div>
??????<button?onClick={()?=>?{this.clickBtn()}}>click?addbutton>
??????<div>val:?{?this.state.val?}div>
????div>)
??}
}
export?default?App;
先看看同步模式下:

再看看異步模式下:

最后 setState 的次數(shù)是 81 次,表明這里的操作在 81 個時間片下進(jìn)行的,每個時間片更新了一次。
總結(jié)
這篇文章前后花費(fèi)時間比較久,看 React 的源碼確實(shí)很痛苦,因?yàn)橹皼]有了解過,剛開始是看一些文章的分析,但是很多模棱兩可的地方,無奈只能在源碼上進(jìn)行 debug,而且一次性看了 React 15、16 兩個版本的代碼,感覺腦子都有些不夠用了。
當(dāng)然這篇文章只是簡單介紹了更新機(jī)制從同步到異步的過程,其實(shí) React 16 的更新除了異步之外,在時間片的劃分、任務(wù)的優(yōu)先級上還有很多細(xì)節(jié),這些東西放到下篇文章來講,不知不覺又是一個新坑。
●?你不知道的 React Hooks(萬字長文,快速入門必備)
·END·
匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程
喜歡本文,點(diǎn)個“在看”告訴我


