欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React深入分析更新的創(chuàng)建源碼

 更新時(shí)間:2023年01月14日 08:53:23   作者:web老猴子  
React組件分為函數(shù)組件與class組件;函數(shù)組件是無(wú)狀態(tài)組件,class稱為類組件;函數(shù)組件只有props,沒(méi)有自己的私有數(shù)據(jù)和生命周期函數(shù);class組件有自己私有數(shù)據(jù)(this.state)和生命周期函數(shù)

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ù)介紹)

從這部分源碼我們不難看出,rendercreateProtal 的用法的聯(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ì)象定義了 enqueueSetStateenqueueReplaceState 以及 enqueueForceUpdate 對(duì)象方法,觀察這兩個(gè)方法會(huì)發(fā)現(xiàn),不同在于enqueueReplaceStateenqueueForceUpdate 會(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è)不同方法,分別為:computeAsyncExpirationcomputeSuspenseExpiration 、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 ,還有 childExpirationTimeroot.firstPendingTime 、root.lastExpiredTime 、root.firstSuspendedTimeroot.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展示解決方案

    這篇文章主要介紹了關(guān)于React項(xiàng)目中的PDF展示解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • React-Router v6實(shí)現(xiàn)頁(yè)面級(jí)按鈕權(quán)限示例詳解

    React-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-10
  • react中setState的執(zhí)行機(jī)制詳解

    react中setState的執(zhí)行機(jī)制詳解

    setState() 的執(zhí)行機(jī)制包括狀態(tài)合并、批量更新、異步更新、虛擬 DOM 比較和渲染組件等步驟,這樣可以提高性能并優(yōu)化渲染過(guò)程,這篇文章主要介紹了react中的setState的執(zhí)行機(jī)制,需要的朋友可以參考下
    2023-10-10
  • react高階組件經(jīng)典應(yīng)用之權(quán)限控制詳解

    react高階組件經(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-09
  • React如何使用axios請(qǐng)求數(shù)據(jù)并把數(shù)據(jù)渲染到組件

    React如何使用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ù)覽

    這篇文章主要介紹了使用react-native-doc-viewer實(shí)現(xiàn)文檔預(yù)覽,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • webpack打包react項(xiàng)目的實(shí)現(xiàn)方法

    webpack打包react項(xiàng)目的實(shí)現(xiàn)方法

    這篇文章主要介紹了webpack打包react項(xiàng)目的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06
  • React改變?cè)貥邮降牟僮鞔a

    React改變?cè)貥邮降牟僮鞔a

    這篇文章主要介紹了React技巧之改變?cè)貥邮?本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • react實(shí)現(xiàn)antd線上主題動(dòng)態(tài)切換功能

    react實(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)題及解決

    記錄React使用connect后,ref.current為null問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05

最新評(píng)論