網站首頁 編程語言 正文
這一章節就來講講ReactDOM.render()
方法的內部實現與流程吧。
因為初始化的源碼文件部分所涵蓋的內容很多,包括創建渲染
、更新渲染
、Fiber樹
的創建與diff
,element
的創建與插入,還包括一些優化算法,所以我就整個的React
執行流程畫了一個簡單的示意圖。
React源碼執行流程圖
從圖中我們很清晰的看到ReactDOM.render()
之后我們的組件具體干了什么事情,那么我們進入源碼文件一探究竟吧。
// packages/react-dom/src/client/ReactDOMLegacy.js export function render( element: React$Element<any>, // 經過babel解析后的element container: Container, // 根組件節點: document.getElementById('root').. callback: ?Function,// 回調 ) { // 做合法容器的驗證(根組件) invariant( isValidContainer(container), 'Target container is not a DOM element.', ); // 開發模式下 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, ); }
所以當前render
函數僅僅只是做了部分邏輯,閱讀React
源碼,給你一個直觀的感受就是他拆分的顆粒度非常的細,很多重復命名的函數,可能是見名知意的變量名只有那么幾個常見的組合吧,這也是React作者的用心良苦吧。
追根究底我們還是得看一看legacyRenderSubtreeIntoContainer
究竟干了些不為人知的事情呢
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // 父級組件 children: ReactNodeList, // 當前元素 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; // 如果有根組件,表示不是初始化渲染,則走下面的批量更新 // 沒有根組件,那么就要去創建根組件了 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); }
- 有根節點的情況下,我們判定為非首次渲染狀態,執行
updateContainer
- 沒有根節點的情況下,我們判定為首次渲染,接著去創建根節點,執行
legacyCreateRootFromDOMContainer
,拿到了root
之后,我們會去觸發執行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.', ); } } // 關注createLegacyRoot return createLegacyRoot( container, shouldHydrate ? { hydrate: true, } : undefined, ); }
createLegacyRoot
export function createLegacyRoot( container: Container, // 容器 options?: RootOptions, ): RootType { //關注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); }
- 我們在這里看到
this._internalRoot
出來了,因為在先前這個值會給到fiberRoot
,所以我們再去看一看這個_internalRoot
是怎么創建出來的 - 相關參考視頻講解:進入學習
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; // 關注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); } } // 關注root return root; }
見名知意關注createContainer
為創建容器,看其源碼
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 { // 關注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; } // 關注createHostRootFiber const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; // 初始化更新隊列 initializeUpdateQueue(uninitializedFiber); return root; }
關注 root.current
、uninitializedFiber.stateNode
這兩個玩意兒,后面有大作用,我們還是看看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); }
一眼望去這里便是對tag
的處理,到了后面便是去創建fiber
節點
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); };
那么主角出來了,就是我們的FiberNode
,這里才走完初始化的創建流程,
所以大致的流程就是上面的圖里畫的那樣子,創建流程我們就告一段落,那我們再去看看更新的流程是怎么玩的。
我們知道除了ReactDOM.render()
會觸發更新流程之外,我們還有setState
、強制更新
、hooks
里面的setXxxx
等等手段可以觸發更新,所謂setState
那么不正好是我們Component
原型上掛的方法嘛。我們回顧一下Component,那些更新都是調用了updater觸發器上的方法,那么我們去看一下這個東西。
const classComponentUpdater = { isMounted, // setState enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); // 獲取更新觸發的時間 const lane = requestUpdateLane(fiber); // 獲取任務優先級 //根據更新觸發時間 + 更新優先級來創建更新任務對象 const update = createUpdate(eventTime, lane); // 創建更新任務對象 // const update: Update<*> = { // eventTime, // 更新時間 // lane, // 優先級 // tag: UpdateState, // 更新類型:0更新,1替換。,2強制替換,3捕獲型更新 // payload: null,// 需要更新的內容 // callback: null, // 更新完后的回調 // next: null, // 指向下一個更新 // }; // 把內容填上 update.payload = payload; if (callback !== undefined && callback !== null) { if (__DEV__) { // 開發環境下腰給個警告 warnOnInvalidCallback(callback, 'setState'); } // 如果有回調,那么加上回調 update.callback = callback; } // const update: Update<*> = { // eventTime, // 更新時間 you // lane, // 優先級 you // tag: UpdateState, // 更新類型:0更新,1替換。,2強制替換,3捕獲型更新 // payload: null,// 需要更新的內容 you // callback: null, // 更新完后的回調 you // next: null, // 指向下一個更新 // }; 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); } }, // 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);// 創建更新任務 // 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); // 推入更新隊列 scheduleUpdateOnFiber(current, lane, eventTime); // 進行調度 return lane; }
我們看到了enqueueSetState
、enqueueReplaceState
、enqueueForceUpdate
還是初始化時候走的updateContainer
都是走了幾乎一樣的邏輯:requestEventTime
=> requestUpdateLane
=> createUpdate
=> enqueueUpdate
=> scheduleUpdateOnFiber
總結
本章從ReactDOM.render()
開始講解了,初始化的時候,根節點的創建與更新流程,以及在類組件原型上掛載的一些更新的方法,但是為什么這一章不直接把他更新流程講完呢?因為下一章要講一下fiberNode
這個東西,簡而言之他只是一個架構概念,并不是React
獨有的,但是現在很有必要一起來看一看這個,那么下一章我們來一起揭開FiberNode
的神秘面紗吧
原文鏈接:https://blog.csdn.net/weixin_59558923/article/details/127401190
相關推薦
- 2022-10-12 antd為Tree組件標題附加操作按鈕功能_Redis
- 2023-04-07 C#中將dateTimePicker初始值設置為空_C#教程
- 2022-10-21 Rust?入門之函數和注釋實例詳解_相關技巧
- 2022-05-09 python中pip安裝庫時出現Read?timed?out解決辦法_python
- 2022-06-06 一文搞懂Redis中String數據類型_Redis
- 2021-12-24 利用FileReader.readAsText()讀取文件內容并保存到服務器
- 2021-12-12 Redis實現分布式鎖的實例講解_Redis
- 2022-09-25 FFmpeg源碼分析:圖像常用操作
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支