React深入分析更新的創(chuàng)建源碼
React
的鮮活生命起源于 ReactDOM.render
,這個(gè)過(guò)程會(huì)為它的一生儲(chǔ)備好很多必需品,我們順著這個(gè)線索,一探嬰兒般 React
應(yīng)用誕生之初的悅?cè)弧?/p>
更新創(chuàng)建的操作我們總結(jié)為以下兩種場(chǎng)景
- ReactDOM.render
- setState
- forceUpdate
ReactDom.render
串聯(lián)該內(nèi)容,一圖以蔽之
首先看到 react-dom/client/ReactDOM
中對(duì)于 ReactDOM
的定義,其中包含我們熟知的方法、不穩(wěn)定方法以及即將廢棄方法。
const ReactDOM: Object = {createPortal,// LegacyfindDOMNode,hydrate,render,unstable_renderSubtreeIntoContainer,unmountComponentAtNode,// Temporary alias since we already shipped React 16 RC with it.// TODO: remove in React 17.unstable_createPortal(...args) {// ...return createPortal(...args);},unstable_batchedUpdates: batchedUpdates,flushSync: flushSync,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {// ...}, };
此處方法均來(lái)自 ./ReactDOMLegacy
,render
方法定義很簡(jiǎn)單,正如我們常使用的那樣,第一個(gè)參數(shù)是組件,第二個(gè)參數(shù)為組件所要掛載的DOM節(jié)點(diǎn),第三個(gè)參數(shù)為回調(diào)函數(shù)。
export function render( element: React$Element<any>,container: DOMContainer,callback: ?Function, ) {// ...return legacyRenderSubtreeIntoContainer(null,element,container,false,callback,); } function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>,children: ReactNodeList,container: DOMContainer,forceHydrate: boolean,callback: ?Function, ) {// TODO: Without `any` type, Flow says "Property cannot be accessed on any// member of intersection type." Whyyyyyy.let root: RootType = (container._reactRootContainer: any);let fiberRoot;if (!root) {// Initial mountroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 初次渲染,不會(huì)將更新標(biāo)記為batched.unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// UpdateupdateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot); }
這段代碼我們不難發(fā)現(xiàn),調(diào)用 ReactDOM.render
時(shí),返回的 parentComponent
是 null,并且初次渲染,不會(huì)進(jìn)行批量策略的更新,而是需要盡快的完成。(batchedUpdates批量更新后續(xù)介紹)
從這部分源碼我們不難看出,render
和 createProtal
的用法的聯(lián)系,通過(guò)DOM容器創(chuàng)建Root節(jié)點(diǎn)的形式
function legacyCreateRootFromDOMContainer( container: DOMContainer,forceHydrate: boolean, ): RootType {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);// First clear any existing content.if (!shouldHydrate) {let warned = false;let rootSibling;while ((rootSibling = container.lastChild)) {// ...container.removeChild(rootSibling);}}return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,); }
createLegacyRoot
定義于 ./ReactDOMRoot
中,指定了創(chuàng)建的DOM容器和一些option設(shè)置,最終會(huì)返回一個(gè) ReactDOMBlockingRoot
。
export function createLegacyRoot( container: DOMContainer,options?: RootOptions, ): RootType {return new ReactDOMBlockingRoot(container, LegacyRoot, options); } function ReactDOMBlockingRoot( container: DOMContainer,tag: RootTag,options: void | RootOptions, ) {this._internalRoot = createRootImpl(container, tag, options); } function createRootImpl( container: DOMContainer,tag: RootTag,options: void | RootOptions, ) {// Tag is either LegacyRoot or Concurrent Root// ...const root = createContainer(container, tag, hydrate, hydrationCallbacks);// ...return root; }
關(guān)鍵點(diǎn)在于,方法最終調(diào)用了 createContainer
來(lái)創(chuàng)建root,而該方法中會(huì)創(chuàng)建我們上一節(jié)所介紹的 FiberRoot
,該對(duì)象在后續(xù)的更新調(diào)度過(guò)程中起著非常重要的作用,到更新調(diào)度內(nèi)容我們?cè)敿?xì)介紹。
在這部分我們看到了兩個(gè)方法,分別是:createContainer、 updateContainer,均出自 react-reconciler/inline.dom
, 最終定義在 ``react-reconciler/src/ReactFiberReconciler` 。創(chuàng)建方法很簡(jiǎn)單,如下
export function createContainer(containerInfo: Container,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,): OpaqueRoot {return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); }
我們繼續(xù)往下看,緊跟著能看到 updateContainer
方法,該方法定義了更新相關(guān)的操作,其中最重要的一個(gè)點(diǎn)就是 expirationTime ,直接譯為中文是過(guò)期時(shí)間,我們想想,此處為何要過(guò)期時(shí)間,這個(gè)過(guò)期的含義是什么呢?這個(gè)過(guò)期時(shí)間是如何計(jì)算的呢?繼續(xù)往下我們可以看到,computeExpirationForFiber
方法用于過(guò)期時(shí)間的計(jì)算,我們先將源碼片段放在此處。
export function updateContainer( element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,callback: ?Function, ): ExpirationTime {const current = container.current;const currentTime = requestCurrentTimeForUpdate();// ...const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,current,suspenseConfig,);// ...const context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}// ...const update = createUpdate(expirationTime, suspenseConfig);// Caution: React DevTools currently depends on this property// being called "element".update.payload = {element};callback = callback === undefined ? null : callback;if (callback !== null) {warningWithoutStack(typeof callback === 'function','render(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callback,);update.callback = callback;}enqueueUpdate(current, update);scheduleWork(current, expirationTime);return expirationTime; }
計(jì)算完更新超時(shí)時(shí)間,而后創(chuàng)建更新對(duì)象 createUpdate
,進(jìn)而將element綁定到update對(duì)象上,如果存在回調(diào)函數(shù),則將回調(diào)函數(shù)也綁定到update對(duì)象上。update對(duì)象創(chuàng)建完成,將update添加到UpdateQueue中,關(guān)于update和UpdateQueue數(shù)據(jù)結(jié)構(gòu)見(jiàn)上一節(jié)講解。至此,開(kāi)始任務(wù)調(diào)度。
setState 與 forceUpdate
這兩個(gè)方法綁定在我們當(dāng)初定義React的文件中,具體定義在 react/src/ReactBaseClasses
中,如下
Component.prototype.setState = function(partialState, callback) {// ...this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; Component.prototype.forceUpdate = function(callback) {this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
這就是為何React基礎(chǔ)上拓展React-Native能輕松自如,因?yàn)镽eact只是做了一些規(guī)范和結(jié)構(gòu)設(shè)定,具體實(shí)現(xiàn)是在React-Dom或React-Native中,如此達(dá)到了平臺(tái)適配性。
Class組件的更新使用 this.setState
,這個(gè)api我們?cè)缫褷€熟于心,對(duì)于對(duì)象組件的更新創(chuàng)建,定義在 react-reconciler/src/ReactFiberClassComponent.js
,classComponentUpdater對(duì)象定義了 enqueueSetState
與 enqueueReplaceState
以及 enqueueForceUpdate
對(duì)象方法,觀察這兩個(gè)方法會(huì)發(fā)現(xiàn),不同在于enqueueReplaceState
和 enqueueForceUpdate
會(huì)在創(chuàng)建的update對(duì)象綁定一個(gè)tag,用于標(biāo)志更新的類型是 ReplaceState
還是 ForceUpdate
,具體實(shí)現(xiàn)我們一起來(lái)看代碼片段。
const classComponentUpdater = {isMounted,enqueueSetState(inst, payload, callback) {const fiber = getInstance(inst);const currentTime = requestCurrentTimeForUpdate();const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);const update = createUpdate(expirationTime, suspenseConfig);update.payload = payload;if (callback !== undefined && callback !== null) {// ...update.callback = callback;}enqueueUpdate(fiber, update);scheduleWork(fiber, expirationTime);},enqueueReplaceState(inst, payload, callback) {const fiber = getInstance(inst);const currentTime = requestCurrentTimeForUpdate();const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);const update = createUpdate(expirationTime, suspenseConfig);update.tag = ReplaceState;update.payload = payload;if (callback !== undefined && callback !== null) {// ...update.callback = callback;}enqueueUpdate(fiber, update);scheduleWork(fiber, expirationTime);},enqueueForceUpdate(inst, callback) {const fiber = getInstance(inst);const currentTime = requestCurrentTimeForUpdate();const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);const update = createUpdate(expirationTime, suspenseConfig);update.tag = ForceUpdate;if (callback !== undefined && callback !== null) {// ...update.callback = callback;}enqueueUpdate(fiber, update);scheduleWork(fiber, expirationTime);}, };
我們也能發(fā)現(xiàn),其實(shí)通過(guò)setState更新的操作實(shí)現(xiàn)和ReactDOM.render基本一致。
1.更新過(guò)期時(shí)間
2.創(chuàng)建Update對(duì)象
3.為update對(duì)象綁定一些屬性,比如 tag
、callback
4.創(chuàng)建的update對(duì)象入隊(duì) (enqueueUpdate)
5.進(jìn)入調(diào)度過(guò)程
expirationTime的作用
expirationTime用于React在調(diào)度和渲染過(guò)程,優(yōu)先級(jí)判斷,針對(duì)不同的操作,有不同響應(yīng)優(yōu)先級(jí),這時(shí)我們通過(guò) currentTime: ExpirationTime
變量與預(yù)定義的優(yōu)先級(jí)EXPIRATION常量計(jì)算得出expirationTime。難道currentTime如我們平時(shí)糟糕代碼中的 Date.now()
?錯(cuò)!如此操作會(huì)產(chǎn)生頻繁計(jì)算導(dǎo)致性能降低,因此我們定義currentTime的計(jì)算規(guī)則。
獲取currentTime
export function requestCurrentTimeForUpdate() {if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {// We're inside React, so it's fine to read the actual time.return msToExpirationTime(now());}// We're not inside React, so we may be in the middle of a browser event.if (currentEventTime !== NoWork) {// Use the same start time for all updates until we enter React again.return currentEventTime;}// This is the first update since React yielded. Compute a new start time.currentEventTime = msToExpirationTime(now());return currentEventTime; }
該方法定義了如何去獲得當(dāng)前時(shí)間,now
方法由 ./SchedulerWithReactIntegration
提供,對(duì)于now方法的定義似乎不太好找,我們通過(guò)斷點(diǎn)調(diào)試 Scheduler_now
,最終能夠發(fā)現(xiàn)時(shí)間的獲取是通過(guò) window.performance.now()
, 緊接著找尋到 msToExpirationTime
定義在 ReactFiberExpirationTime.js
,定義了expirationTime相關(guān)處理邏輯。
不同的expirationTime
閱讀到 react-reconciler/src/ReactFilberExpirationTime
,對(duì)于expirationTime的計(jì)算有三個(gè)不同方法,分別為:computeAsyncExpiration
、computeSuspenseExpiration
、computeInteractiveExpiration
。這三個(gè)方法均接收三個(gè)參數(shù),第一個(gè)參數(shù)均為以上獲取的 currentTime
,第二個(gè)參數(shù)為約定的超時(shí)時(shí)間,第三個(gè)參數(shù)與批量更新的粒度有關(guān)。
export const Sync = MAX_SIGNED_31_BIT_INT; export const Batched = Sync - 1; const UNIT_SIZE = 10; const MAGIC_NUMBER_OFFSET = Batched - 1; // 1 unit of expiration time represents 10ms. export function msToExpirationTime(ms: number): ExpirationTime {// Always add an offset so that we don't clash with the magic number for NoWork.return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0); } export function expirationTimeToMs(expirationTime: ExpirationTime): number {return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE; } function ceiling(num: number, precision: number): number {return (((num / precision) | 0) + 1) * precision; } function computeExpirationBucket( currentTime,expirationInMs,bucketSizeMs, ): ExpirationTime {return (MAGIC_NUMBER_OFFSET -ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,bucketSizeMs / UNIT_SIZE,)); } // TODO: This corresponds to Scheduler's NormalPriority, not LowPriority. Update // the names to reflect. export const LOW_PRIORITY_EXPIRATION = 5000; export const LOW_PRIORITY_BATCH_SIZE = 250; export function computeAsyncExpiration( currentTime: ExpirationTime, ): ExpirationTime {return computeExpirationBucket(currentTime,LOW_PRIORITY_EXPIRATION,LOW_PRIORITY_BATCH_SIZE,); } export function computeSuspenseExpiration( currentTime: ExpirationTime,timeoutMs: number, ): ExpirationTime {// TODO: Should we warn if timeoutMs is lower than the normal pri expiration time?return computeExpirationBucket(currentTime,timeoutMs,LOW_PRIORITY_BATCH_SIZE,); } export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150; export const HIGH_PRIORITY_BATCH_SIZE = 100; export function computeInteractiveExpiration(currentTime: ExpirationTime) {return computeExpirationBucket(currentTime,HIGH_PRIORITY_EXPIRATION,HIGH_PRIORITY_BATCH_SIZE,); }
重點(diǎn)在于 ceil
方法的定義,方法傳遞兩個(gè)參數(shù),需要求值的number和期望精度precision,不妨隨意帶入兩個(gè)值觀察該函數(shù)的作用,number = 100,precision = 10,那么函數(shù)返回值為 (((100 / 10) | 0) + 1) * 10,我們保持precision值不變,更改number會(huì)發(fā)現(xiàn),當(dāng)我們的值在100-110之間時(shí),該函數(shù)返回的值相同。哦!此時(shí)恍然大悟,原來(lái)這個(gè)方法就是保證在同一個(gè)bucket中的更新獲取到相同的過(guò)期時(shí)間 expirationTime
,就能夠?qū)崿F(xiàn)在較短時(shí)間間隔內(nèi)的更新創(chuàng)建能夠__合并處理__。
關(guān)于超時(shí)時(shí)間的處理是很復(fù)雜的,除了我們看到的 expirationTime
,還有 childExpirationTime
、root.firstPendingTime
、root.lastExpiredTime
、root.firstSuspendedTime
、root.lastSuspendedTime
,root下的相關(guān)屬性標(biāo)記了其下子節(jié)點(diǎn)fiber的expirationTime的次序,構(gòu)成處理優(yōu)先級(jí)的次序,childExpirationTime
則是在遍歷子樹(shù)時(shí),更新其 childExpirationTime
值為子節(jié)點(diǎn) expirationTime
。
以上是React創(chuàng)建更新的核心流程,任務(wù)調(diào)度我們下次再見(jiàn)。
到此這篇關(guān)于React深入分析更新的創(chuàng)建源碼的文章就介紹到這了,更多相關(guān)React更新的創(chuàng)建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于React項(xiàng)目中的PDF展示解決方案
這篇文章主要介紹了關(guān)于React項(xiàng)目中的PDF展示解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07React-Router v6實(shí)現(xiàn)頁(yè)面級(jí)按鈕權(quán)限示例詳解
這篇文章主要介紹了使用 reac+reactRouter來(lái)實(shí)現(xiàn)頁(yè)面級(jí)的按鈕權(quán)限功能,這篇文章分三部分,實(shí)現(xiàn)思路、代碼實(shí)現(xiàn)、踩坑記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-10-10react中setState的執(zhí)行機(jī)制詳解
setState() 的執(zhí)行機(jī)制包括狀態(tài)合并、批量更新、異步更新、虛擬 DOM 比較和渲染組件等步驟,這樣可以提高性能并優(yōu)化渲染過(guò)程,這篇文章主要介紹了react中的setState的執(zhí)行機(jī)制,需要的朋友可以參考下2023-10-10react高階組件經(jīng)典應(yīng)用之權(quán)限控制詳解
在React中,高階組件是重用組件邏輯的一項(xiàng)高級(jí)技術(shù)。下面這篇文章主要給大家介紹了關(guān)于react高階組件經(jīng)典應(yīng)用之權(quán)限控制的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09React如何使用axios請(qǐng)求數(shù)據(jù)并把數(shù)據(jù)渲染到組件
這篇文章主要介紹了React如何使用axios請(qǐng)求數(shù)據(jù)并把數(shù)據(jù)渲染到組件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08使用react-native-doc-viewer實(shí)現(xiàn)文檔預(yù)覽
這篇文章主要介紹了使用react-native-doc-viewer實(shí)現(xiàn)文檔預(yù)覽,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09webpack打包react項(xiàng)目的實(shí)現(xiàn)方法
這篇文章主要介紹了webpack打包react項(xiàng)目的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06react實(shí)現(xiàn)antd線上主題動(dòng)態(tài)切換功能
這篇文章主要介紹了react實(shí)現(xiàn)antd線上主題動(dòng)態(tài)切換功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08記錄React使用connect后,ref.current為null問(wèn)題及解決
記錄React使用connect后,ref.current為null問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05