react組件的創(chuàng)建與更新實(shí)現(xiàn)流程詳解
這一章節(jié)就來(lái)講講ReactDOM.render()
方法的內(nèi)部實(shí)現(xiàn)與流程吧。
因?yàn)槌跏蓟脑创a文件部分所涵蓋的內(nèi)容很多,包括創(chuàng)建渲染
、更新渲染
、Fiber樹(shù)
的創(chuàng)建與diff
,element
的創(chuàng)建與插入,還包括一些優(yōu)化算法,所以我就整個(gè)的React
執(zhí)行流程畫(huà)了一個(gè)簡(jiǎn)單的示意圖。
React源碼執(zhí)行流程圖
從圖中我們很清晰的看到ReactDOM.render()
之后我們的組件具體干了什么事情,那么我們進(jìn)入源碼文件一探究竟吧。
// packages/react-dom/src/client/ReactDOMLegacy.js export function render( element: React$Element<any>, // 經(jīng)過(guò)babel解析后的element container: Container, // 根組件節(jié)點(diǎn): document.getElementById('root').. callback: ?Function,// 回調(diào) ) { // 做合法容器的驗(yàn)證(根組件) invariant( isValidContainer(container), 'Target container is not a DOM element.', ); // 開(kāi)發(fā)模式下 if (__DEV__) { const isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; if (isModernRoot) { console.error( 'You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)?', ); } } // 返回 legacyRenderSubtreeIntoContainer return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); }
所以當(dāng)前render
函數(shù)僅僅只是做了部分邏輯,閱讀React
源碼,給你一個(gè)直觀的感受就是他拆分的顆粒度非常的細(xì),很多重復(fù)命名的函數(shù),可能是見(jiàn)名知意的變量名只有那么幾個(gè)常見(jiàn)的組合吧,這也是React作者的用心良苦吧。
追根究底我們還是得看一看legacyRenderSubtreeIntoContainer
究竟干了些不為人知的事情呢
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // 父級(jí)組件 children: ReactNodeList, // 當(dāng)前元素 container: Container, // 容器 eg:getElementById('root') forceHydrate: boolean, callback: ?Function, ) { if (__DEV__) { topLevelUpdateWarnings(container); warnOnInvalidCallback(callback === undefined ? null : callback, 'render'); } // 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; // 如果有根組件,表示不是初始化渲染,則走下面的批量更新 // 沒(méi)有根組件,那么就要去創(chuàng)建根組件了 if (!root) { // 初始化掛載 root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot; if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // 不必要的批量更新 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); }; } // 批量更新 updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }
- 有根節(jié)點(diǎn)的情況下,我們判定為非首次渲染狀態(tài),執(zhí)行
updateContainer
- 沒(méi)有根節(jié)點(diǎn)的情況下,我們判定為首次渲染,接著去創(chuàng)建根節(jié)點(diǎn),執(zhí)行
legacyCreateRootFromDOMContainer
,拿到了root
之后,我們會(huì)去觸發(fā)執(zhí)行updateContainer
legacyCreateRootFromDOMContainer
function legacyCreateRootFromDOMContainer( container: Container, // 容器 forceHydrate: boolean, // value:false ): RootType { const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. if (!shouldHydrate) { let warned = false; let rootSibling; while ((rootSibling = container.lastChild)) { if (__DEV__) { if ( !warned && rootSibling.nodeType === ELEMENT_NODE && (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME) ) { warned = true; console.error( 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.', ); } } container.removeChild(rootSibling); } } if (__DEV__) { if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) { warnedAboutHydrateAPI = true; console.warn( 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v18. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', ); } } // 關(guān)注createLegacyRoot return createLegacyRoot( container, shouldHydrate ? { hydrate: true, } : undefined, ); }
createLegacyRoot
export function createLegacyRoot( container: Container, // 容器 options?: RootOptions, ): RootType { //關(guān)注ReactDOMBlockingRoot return new ReactDOMBlockingRoot(container, LegacyRoot, options); }
ReactDOMBlockingRoot
function ReactDOMBlockingRoot( container: Container, // 容器 tag: RootTag, // LegacyRoot = 0;BlockingRoot = 1;ConcurrentRoot = 2; options: void | RootOptions, ) { this._internalRoot = createRootImpl(container, tag, options); }
- 我們?cè)谶@里看到
this._internalRoot
出來(lái)了,因?yàn)樵谙惹斑@個(gè)值會(huì)給到fiberRoot
,所以我們?cè)偃タ匆豢催@個(gè)_internalRoot
是怎么創(chuàng)建出來(lái)的 - 相關(guān)參考視頻講解:進(jìn)入學(xué)習(xí)
createRootImpl
function createRootImpl( container: Container, tag: RootTag, options: void | RootOptions, ) { // Tag is either LegacyRoot or Concurrent Root const hydrate = options != null && options.hydrate === true; const hydrationCallbacks = (options != null && options.hydrationOptions) || null; const mutableSources = (options != null && options.hydrationOptions != null && options.hydrationOptions.mutableSources) || null; // 關(guān)注createContainer const root = createContainer(container, tag, hydrate, hydrationCallbacks); markContainerAsRoot(root.current, container); const containerNodeType = container.nodeType; if (enableEagerRootListeners) { const rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; listenToAllSupportedEvents(rootContainerElement); } else { if (hydrate && tag !== LegacyRoot) { const doc = containerNodeType === DOCUMENT_NODE ? container : container.ownerDocument; // We need to cast this because Flow doesn't work // with the hoisted containerNodeType. If we inline // it, then Flow doesn't complain. We intentionally // hoist it to reduce code-size. eagerlyTrapReplayableEvents(container, ((doc: any): Document)); } else if ( containerNodeType !== DOCUMENT_FRAGMENT_NODE && containerNodeType !== DOCUMENT_NODE ) { ensureListeningTo(container, 'onMouseEnter', null); } } if (mutableSources) { for (let i = 0; i < mutableSources.length; i++) { const mutableSource = mutableSources[i]; registerMutableSourceForHydration(root, mutableSource); } } // 關(guān)注root return root; }
見(jiàn)名知意關(guān)注createContainer
為創(chuàng)建容器,看其源碼
createContainer
// packages/react-reconciler/src/ReactFiberReconciler.old.js export function createContainer( containerInfo: Container, // 容器 tag: RootTag, // LegacyRoot = 0;BlockingRoot = 1;ConcurrentRoot = 2; hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): OpaqueRoot { // 關(guān)注createFiberRoot return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); }
createFiberRoot
export function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot { const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } // 關(guān)注createHostRootFiber const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; // 初始化更新隊(duì)列 initializeUpdateQueue(uninitializedFiber); return root; }
關(guān)注 root.current
、uninitializedFiber.stateNode
這兩個(gè)玩意兒,后面有大作用,我們還是看看createHostRootFiber
吧
createHostRootFiber
export function createHostRootFiber(tag: RootTag): Fiber { let mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode | BlockingMode | StrictMode; } else if (tag === BlockingRoot) { mode = BlockingMode | StrictMode; } else { mode = NoMode; } if (enableProfilerTimer && isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; } return createFiber(HostRoot, null, null, mode); }
一眼望去這里便是對(duì)tag
的處理,到了后面便是去創(chuàng)建fiber
節(jié)點(diǎn)
createFiber
const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ): Fiber { // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors return new FiberNode(tag, pendingProps, key, mode); };
那么主角出來(lái)了,就是我們的FiberNode
,這里才走完初始化的創(chuàng)建流程,
所以大致的流程就是上面的圖里畫(huà)的那樣子,創(chuàng)建流程我們就告一段落,那我們?cè)偃タ纯锤碌牧鞒淌窃趺赐娴摹?/p>
我們知道除了ReactDOM.render()
會(huì)觸發(fā)更新流程之外,我們還有setState
、強(qiáng)制更新
、hooks
里面的setXxxx
等等手段可以觸發(fā)更新,所謂setState
那么不正好是我們Component
原型上掛的方法嘛。我們回顧一下Component,那些更新都是調(diào)用了updater觸發(fā)器上的方法,那么我們?nèi)タ匆幌逻@個(gè)東西。
const classComponentUpdater = { isMounted, // setState enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); // 獲取更新觸發(fā)的時(shí)間 const lane = requestUpdateLane(fiber); // 獲取任務(wù)優(yōu)先級(jí) //根據(jù)更新觸發(fā)時(shí)間 + 更新優(yōu)先級(jí)來(lái)創(chuàng)建更新任務(wù)對(duì)象 const update = createUpdate(eventTime, lane); // 創(chuàng)建更新任務(wù)對(duì)象 // const update: Update<*> = { // eventTime, // 更新時(shí)間 // lane, // 優(yōu)先級(jí) // tag: UpdateState, // 更新類型:0更新,1替換。,2強(qiáng)制替換,3捕獲型更新 // payload: null,// 需要更新的內(nèi)容 // callback: null, // 更新完后的回調(diào) // next: null, // 指向下一個(gè)更新 // }; // 把內(nèi)容填上 update.payload = payload; if (callback !== undefined && callback !== null) { if (__DEV__) { // 開(kāi)發(fā)環(huán)境下腰給個(gè)警告 warnOnInvalidCallback(callback, 'setState'); } // 如果有回調(diào),那么加上回調(diào) update.callback = callback; } // const update: Update<*> = { // eventTime, // 更新時(shí)間 you // lane, // 優(yōu)先級(jí) you // tag: UpdateState, // 更新類型:0更新,1替換。,2強(qiáng)制替換,3捕獲型更新 // payload: null,// 需要更新的內(nèi)容 you // callback: null, // 更新完后的回調(diào) you // next: null, // 指向下一個(gè)更新 // }; enqueueUpdate(fiber, update);// 推入更新隊(duì)列 scheduleUpdateOnFiber(fiber, lane, eventTime);// 調(diào)度 if (__DEV__) { if (enableDebugTracing) { if (fiber.mode & DebugTracingMode) { const name = getComponentName(fiber.type) || 'Unknown'; logStateUpdateScheduled(name, lane, payload); } } } if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }, // replaceState enqueueReplaceState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); const update = createUpdate(eventTime, lane); update.tag = ReplaceState; update.payload = payload; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'replaceState'); } update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); if (__DEV__) { if (enableDebugTracing) { if (fiber.mode & DebugTracingMode) { const name = getComponentName(fiber.type) || 'Unknown'; logStateUpdateScheduled(name, lane, payload); } } } if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }, // forceUpdate enqueueForceUpdate(inst, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); const update = createUpdate(eventTime, lane); update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'forceUpdate'); } update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); if (__DEV__) { if (enableDebugTracing) { if (fiber.mode & DebugTracingMode) { const name = getComponentName(fiber.type) || 'Unknown'; logForceUpdateScheduled(name, lane); } } } if (enableSchedulingProfiler) { markForceUpdateScheduled(fiber, lane); } }, };
updateContainer
export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): Lane { if (__DEV__) { onScheduleRoot(container, element); } const current = container.current; const eventTime = requestEventTime(); if (__DEV__) { // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests if ('undefined' !== typeof jest) { warnIfUnmockedScheduler(current); warnIfNotScopedWithMatchingAct(current); } } const lane = requestUpdateLane(current); if (enableSchedulingProfiler) { markRenderScheduled(lane); } const context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } if (__DEV__) { if ( ReactCurrentFiberIsRendering && ReactCurrentFiberCurrent !== null && !didWarnAboutNestedUpdates ) { didWarnAboutNestedUpdates = true; console.error( 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown', ); } } const update = createUpdate(eventTime, lane);// 創(chuàng)建更新任務(wù) // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { if (__DEV__) { if (typeof callback !== 'function') { console.error( 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback, ); } } update.callback = callback; } enqueueUpdate(current, update); // 推入更新隊(duì)列 scheduleUpdateOnFiber(current, lane, eventTime); // 進(jìn)行調(diào)度 return lane; }
我們看到了enqueueSetState
、enqueueReplaceState
、enqueueForceUpdate
還是初始化時(shí)候走的updateContainer
都是走了幾乎一樣的邏輯:requestEventTime
=> requestUpdateLane
=> createUpdate
=> enqueueUpdate
=> scheduleUpdateOnFiber
總結(jié)
本章從ReactDOM.render()
開(kāi)始講解了,初始化的時(shí)候,根節(jié)點(diǎn)的創(chuàng)建與更新流程,以及在類組件原型上掛載的一些更新的方法,但是為什么這一章不直接把他更新流程講完呢?因?yàn)橄乱徽乱v一下fiberNode
這個(gè)東西,簡(jiǎn)而言之他只是一個(gè)架構(gòu)概念,并不是React
獨(dú)有的,但是現(xiàn)在很有必要一起來(lái)看一看這個(gè),那么下一章我們來(lái)一起揭開(kāi)FiberNode
的神秘面紗吧
到此這篇關(guān)于react組件的創(chuàng)建與更新實(shí)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)react組件的創(chuàng)建與更新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Electron打包React生成桌面應(yīng)用方法詳解
這篇文章主要介紹了React+Electron快速創(chuàng)建并打包成桌面應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12使用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的待辦事項(xiàng)列表的示例代碼
這篇文章我們將詳細(xì)講解如何建立一個(gè)這樣簡(jiǎn)單的列表,文章通過(guò)代碼示例介紹的非常詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-08-08Remix后臺(tái)開(kāi)發(fā)之remix-antd-admin配置過(guò)程
這篇文章主要為大家介紹了Remix后臺(tái)開(kāi)發(fā)之remix-antd-admin配置過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Redux DevTools不能顯示數(shù)據(jù)問(wèn)題
這篇文章主要介紹了Redux DevTools不能顯示數(shù)據(jù)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01React實(shí)現(xiàn)倒計(jì)時(shí)功能組件
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)React實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)功能組件,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-09-09解析react?函數(shù)組件輸入卡頓問(wèn)題?usecallback?react.memo
useMemo是一個(gè)react hook,我們可以使用它在組件中包裝函數(shù)??梢允褂盟鼇?lái)確保該函數(shù)中的值僅在依賴項(xiàng)之一發(fā)生變化時(shí)才重新計(jì)算,這篇文章主要介紹了react?函數(shù)組件輸入卡頓問(wèn)題?usecallback?react.memo,需要的朋友可以參考下2022-07-07使用React和Redux Toolkit實(shí)現(xiàn)用戶登錄功能
在React中,用戶登錄功能是一個(gè)常見(jiàn)的需求,為了實(shí)現(xiàn)該功能,需要對(duì)用戶輸入的用戶名和密碼進(jìn)行驗(yàn)證,并將驗(yàn)證結(jié)果保存到應(yīng)用程序狀態(tài)中,在React中,可以使用Redux Toolkit來(lái)管理應(yīng)用程序狀態(tài),從而實(shí)現(xiàn)用戶登錄功能,需要詳細(xì)了解可以參考下文2023-05-05