摘要:虛擬的初始化在閱讀源碼前,我們先提出一個問題,是如何將虛擬轉換為真實的呢有問題以后我們才會更有目標的閱讀代碼,下面我們就帶著這個問題去思考。對類型的處理主要是更新屬性,然后通過創建節點,并添加進父節點。最后所有的虛擬都將轉為真實。
前言
本文的主要目的是閱讀源碼的過程中做下筆記和分享給有需要的小伙伴,可能會有紕漏和錯誤,請讀者自行判斷,頭一次寫閱讀代碼的文章,可能寫得有點亂,有什么問題歡迎一起探討一起進步。
React的版本為16.4,主分支的代碼,只貼出部分關鍵代碼,完整代碼請到Github查看。
虛擬DOM的初始化 React.createElement在閱讀源碼前,我們先提出一個問題,React是如何將虛擬DOM轉換為真實的DOM呢?有問題以后我們才會更有目標的閱讀代碼,下面我們就帶著這個問題去思考。
在平時工作中我們常常用JSX語法來創建React元素實例,但他們最后都會通過打包工具編譯成原生的JS代碼,通過React.createElement來創建。例如:
// class ReactComponent extends React.Component { // render() { // returnHello React
; // } // } // 以上代碼會編譯為: class ReactComponent extends React.Component { render() { React.createElement( "p", { className: "class"}, "Hello React" ) } } //React.createElement(ReactComponent, { someProp: "prop" }, null);
這樣我們就可以創建得到React元素實例。
先來看看createElement的主要源碼(部分代碼省略):
function createElement(type, config, children) { let propName; const props = {}; let key = null; let ref = null; let self = null; let source = null; if (config != null) { if (hasValidRef(config)) { // 如果有ref,將他取出來 ref = config.ref; } if (hasValidKey(config)) { // 如果有key,將他取出來 key = "" + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { // 將除ref,key等這些特殊的屬性放到新的props對象里 props[propName] = config[propName]; } } } // 獲取子元素 const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; } // 添加默認props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
const ReactElement = function(type, key, ref, self, source, owner, props) { // 最終得到的React元素 const element = { // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner, }; return element; };
是不是很簡單呢,主要是把我們傳進去的東西組成一個React元素對象,而type就是我們傳進去的組件類型,他可以是一個類、函數或字符串(如"div")。
ReactDom.render雖然我們已經得到創建好的React元素,但React有是如何把React元素轉換為我們最終想要的DOM呢?就是通過ReactDom.render函數啦。
ReactDom.render( React.createElement(App), document.getElementById("root") );
ReactDom.render的定義:
render( element: React$Element, container: DOMContainer, callback: ?Function, ) { return legacyRenderSubtreeIntoContainer( null, /* 父組件 */ element, /* React元素 */ container, /* DOM容器 */ false, callback, ); }
legacyRenderSubtreeIntoContainer先獲取到React根容器對象(只貼部分代碼):
... root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); ...
因代碼過多只貼出通過legacyCreateRootFromDOMContainer最終得到的React根容器對象:
const NoWork = 0; { _internalRoot: { current: uninitializedFiber, // null containerInfo: containerInfo, // DOM容器 pendingChildren: null, earliestPendingTime: NoWork, latestPendingTime: NoWork, earliestSuspendedTime: NoWork, latestSuspendedTime: NoWork, latestPingedTime: NoWork, didError: false, pendingCommitExpirationTime: NoWork, finishedWork: null, context: null, pendingContext: null, hydrate, nextExpirationTimeToWorkOn: NoWork, expirationTime: NoWork, firstBatch: null, nextScheduledRoot: null, }, render: (children: ReactNodeList, callback: ?() => mixed) => Work, legacy_renderSubtreeIntoContainer: ( parentComponent: ?React$Component, children: ReactNodeList, callback: ?() => mixed ) => Work, createBatch: () => Batch }
在初始化React根容器對象root后,調用root.render開始虛擬DOM的渲染過程。
DOMRenderer.unbatchedUpdates(() => { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } });
因代碼量過大,就不逐一貼出詳細代碼,只貼出主要的函數的調用過程。
root.render(children, callback) -> DOMRenderer.updateContainer(children, root, null, work._onCommit) -> updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, callback, ) -> scheduleRootUpdate(current, element, expirationTime, callback) -> scheduleWork(current, expirationTime) -> requestWork(root, rootExpirationTime) -> performWorkOnRoot(root, Sync, false) -> renderRoot(root, false) -> workLoop(isYieldy) -> performUnitOfWork(nextUnitOfWork: Fiber) => Fiber | null -> beginWork(current, workInProgress, nextRenderExpirationTime)Fiber
Fiber類型:
type Fiber = {| tag: TypeOfWork, key: null | string, // The function/class/module associated with this fiber. type: any, return: Fiber | null, // Singly Linked List Tree Structure. child: Fiber | null, sibling: Fiber | null, index: number, ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject, memoizedProps: any, // The props used to create the output. updateQueue: UpdateQueuebeginWork| null, memoizedState: any, mode: TypeOfMode, effectTag: TypeOfSideEffect, nextEffect: Fiber | null, firstEffect: Fiber | null, lastEffect: Fiber | null, expirationTime: ExpirationTime, alternate: Fiber | null, actualDuration?: number, actualStartTime?: number, selfBaseTime?: number, treeBaseTime?: number, _debugID?: number, _debugSource?: Source | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, |};
按以上函數調用過程,我們來到beginWork函數,它的作用主要是根據Fiber對象的tag來對組件進行mount或update:
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { if (enableProfilerTimer) { if (workInProgress.mode & ProfileMode) { markActualRenderTimeStarted(workInProgress); } } if ( workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime ) { return bailoutOnLowPriority(current, workInProgress); } // 根據組件類型來進行不同處理 switch (workInProgress.tag) { case IndeterminateComponent: // 不確定的組件類型 return mountIndeterminateComponent( current, workInProgress, renderExpirationTime, ); case FunctionalComponent: // 函數類型的組件 return updateFunctionalComponent(current, workInProgress); case ClassComponent: // 類類型的組件,我們這次主要看這個 return updateClassComponent( current, workInProgress, renderExpirationTime, ); case HostRoot: return updateHostRoot(current, workInProgress, renderExpirationTime); case HostComponent: return updateHostComponent(current, workInProgress, renderExpirationTime); case HostText: return updateHostText(current, workInProgress); case TimeoutComponent: return updateTimeoutComponent( current, workInProgress, renderExpirationTime, ); case HostPortal: return updatePortalComponent( current, workInProgress, renderExpirationTime, ); case ForwardRef: return updateForwardRef(current, workInProgress); case Fragment: return updateFragment(current, workInProgress); case Mode: return updateMode(current, workInProgress); case Profiler: return updateProfiler(current, workInProgress); case ContextProvider: return updateContextProvider( current, workInProgress, renderExpirationTime, ); case ContextConsumer: return updateContextConsumer( current, workInProgress, renderExpirationTime, ); default: invariant( false, "Unknown unit of work tag. This error is likely caused by a bug in " + "React. Please file an issue.", ); } }updateClassComponent
updateClassComponent的作用是對未初始化的類組件進行初始化,對已經初始化的組件更新重用
function updateClassComponent( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { const hasContext = pushLegacyContextProvider(workInProgress); let shouldUpdate; if (current === null) { if (workInProgress.stateNode === null) { // 如果還沒創建實例,初始化 constructClassInstance( workInProgress, workInProgress.pendingProps, renderExpirationTime, ); mountClassInstance(workInProgress, renderExpirationTime); shouldUpdate = true; } else { // 如果已經創建實例,則重用實例 shouldUpdate = resumeMountClassInstance( workInProgress, renderExpirationTime, ); } } else { shouldUpdate = updateClassInstance( current, workInProgress, renderExpirationTime, ); } return finishClassComponent( current, workInProgress, shouldUpdate, hasContext, renderExpirationTime, ); }constructClassInstance
實例化類組件:
function constructClassInstance( workInProgress: Fiber, props: any, renderExpirationTime: ExpirationTime, ): any { const ctor = workInProgress.type; // 我們傳進去的那個類 const unmaskedContext = getUnmaskedContext(workInProgress); const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyContextObject; const instance = new ctor(props, context); // 創建實例 const state = (workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null); adoptClassInstance(workInProgress, instance); if (needsContext) { cacheContext(workInProgress, unmaskedContext, context); } return instance; }adoptClassInstance
function adoptClassInstance(workInProgress: Fiber, instance: any): void { instance.updater = classComponentUpdater; workInProgress.stateNode = instance; // 將實例賦值給stateNode屬性 }mountClassInstance
下面的代碼就有我們熟悉的componentWillMount生命周期出現啦,不過新版React已經不建議使用它。
function mountClassInstance( workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): void { const ctor = workInProgress.type; const instance = workInProgress.stateNode; const props = workInProgress.pendingProps; const unmaskedContext = getUnmaskedContext(workInProgress); instance.props = props; instance.state = workInProgress.memoizedState; instance.refs = emptyRefsObject; instance.context = getMaskedContext(workInProgress, unmaskedContext); let updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, props, instance, renderExpirationTime, ); instance.state = workInProgress.memoizedState; } const getDerivedStateFromProps = workInProgress.type.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === "function") { // React新的生命周期函數 applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props); instance.state = workInProgress.memoizedState; } if ( typeof ctor.getDerivedStateFromProps !== "function" && typeof instance.getSnapshotBeforeUpdate !== "function" && (typeof instance.UNSAFE_componentWillMount === "function" || typeof instance.componentWillMount === "function") ) { // 如果沒有使用getDerivedStateFromProps而使用componentWillMount,兼容舊版 callComponentWillMount(workInProgress, instance); updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, props, instance, renderExpirationTime, ); instance.state = workInProgress.memoizedState; } } if (typeof instance.componentDidMount === "function") { workInProgress.effectTag |= Update; } }finishClassComponent
調用組件實例的render函數獲取需渲染的子元素,并把子元素進行處理為Fiber類型,處理state和props:
function finishClassComponent( current: Fiber | null, workInProgress: Fiber, shouldUpdate: boolean, hasContext: boolean, renderExpirationTime: ExpirationTime, ) { markRef(current, workInProgress); const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect; if (!shouldUpdate && !didCaptureError) { if (hasContext) { invalidateContextProvider(workInProgress, false); } return bailoutOnAlreadyFinishedWork(current, workInProgress); } const ctor = workInProgress.type; const instance = workInProgress.stateNode; ReactCurrentOwner.current = workInProgress; let nextChildren; if ( didCaptureError && (!enableGetDerivedStateFromCatch || typeof ctor.getDerivedStateFromCatch !== "function") ) { nextChildren = null; if (enableProfilerTimer) { stopBaseRenderTimerIfRunning(); } } else { if (__DEV__) { ... } else { // 調用render函數獲取子元素 nextChildren = instance.render(); } } workInProgress.effectTag |= PerformedWork; if (didCaptureError) { reconcileChildrenAtExpirationTime( current, workInProgress, null, renderExpirationTime, ); workInProgress.child = null; } // 把子元素轉換為Fiber類型 // 如果子元素數量大于一(即為數組)的時候, // 返回第一個Fiber類型子元素 reconcileChildrenAtExpirationTime( current, workInProgress, nextChildren, renderExpirationTime, ); // 處理state memoizeState(workInProgress, instance.state); // 處理props memoizeProps(workInProgress, instance.props); if (hasContext) { invalidateContextProvider(workInProgress, true); } // 返回Fiber類型的子元素給beginWork函數, // 一直返回到workLoop函數(看上面的調用過程) return workInProgress.child; }workLoop
我們再回頭看下workLoop和performUnitOfWork函數(看上面的函數調用過程),當benginWork對不同類型組件完成相應處理返回子元素后,workLoop繼續通過performUnitOfWork來調用benginWork對子元素進行處理,從而遍歷虛擬DOM樹:
function workLoop(isYieldy) { if (!isYieldy) { while (nextUnitOfWork !== null) { // 遍歷整棵虛擬DOM樹 nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } else { while (nextUnitOfWork !== null && !shouldYield()) { // 遍歷整棵虛擬DOM樹 nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } if (enableProfilerTimer) { pauseActualRenderTimerIfRunning(); } } }performUnitOfWork
這個函數很接近把虛擬DOM轉換為真實DOM的代碼啦,當遍歷完成一顆虛擬DOM的子樹后(beginWork返回null,即已沒有子元素),調用completeUnitOfWork函數開始轉換:
function performUnitOfWork(workInProgress: Fiber): Fiber | null { const current = workInProgress.alternate; startWorkTimer(workInProgress); let next; if (enableProfilerTimer) { if (workInProgress.mode & ProfileMode) { startBaseRenderTimer(); } next = beginWork(current, workInProgress, nextRenderExpirationTime); if (workInProgress.mode & ProfileMode) { recordElapsedBaseRenderTimeIfRunning(workInProgress); stopBaseRenderTimerIfRunning(); } } else { next = beginWork(current, workInProgress, nextRenderExpirationTime); } if (next === null) { next = completeUnitOfWork(workInProgress); } ReactCurrentOwner.current = null; return next; }completeUnitOfWork
此函數作用為先將當前Fiber元素轉換為真實DOM節點,然后在看有無兄弟節點,若有則返回給上層函數處理完后再調用此函數進行轉換;否則查看有無父節點,若有則轉換父節點。
function completeUnitOfWork(workInProgress: Fiber): Fiber | null { while (true) { const current = workInProgress.alternate; const returnFiber = workInProgress.return; const siblingFiber = workInProgress.sibling; if ((workInProgress.effectTag & Incomplete) === NoEffect) { // 調用completeWork轉換虛擬DOM let next = completeWork( current, workInProgress, nextRenderExpirationTime, ); stopWorkTimer(workInProgress); resetExpirationTime(workInProgress, nextRenderExpirationTime); if (next !== null) { stopWorkTimer(workInProgress); return next; } // 處理完當前節點后 if (siblingFiber !== null) { // 如果有兄弟節點,則將其返回 return siblingFiber; } else if (returnFiber !== null) { // 沒有兄弟節點而有父節點,則處理父節點 workInProgress = returnFiber; continue; } else { return null; } } else { ... } return null; }completeWork
根據Fiber的類型進行處理和拋出錯誤,我們主要看HostComponent類型。對HostComponent類型的處理主要是更新屬性,然后通過createInstance創建DOM節點,并添加進父節點。
function completeWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { const newProps = workInProgress.pendingProps; if (enableProfilerTimer) { if (workInProgress.mode & ProfileMode) { recordElapsedActualRenderTime(workInProgress); } } switch (workInProgress.tag) { ... case HostComponent: { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null) { // 更新屬性 const oldProps = current.memoizedProps; const instance: Instance = workInProgress.stateNode; const currentHostContext = getHostContext(); const updatePayload = prepareUpdate( instance, type, oldProps, newProps, rootContainerInstance, currentHostContext, ); updateHostComponent( current, workInProgress, updatePayload, type, oldProps, newProps, rootContainerInstance, currentHostContext, ); if (current.ref !== workInProgress.ref) { markRef(workInProgress); } } else { if (!newProps) { ... return null; } const currentHostContext = getHostContext(); let wasHydrated = popHydrationState(workInProgress); if (wasHydrated) { if ( prepareToHydrateHostInstance( workInProgress, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } } else { // 創建并返回DOM元素 let instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); // 將此DOM節點添加進父節點 appendAllChildren(instance, workInProgress); if ( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } workInProgress.stateNode = instance; } if (workInProgress.ref !== null) { // If there is a ref on a host node we need to schedule a callback markRef(workInProgress); } } return null; } ... } }createInstance
function createInstance( type: string, props: Props, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: Object, ): Instance { let parentNamespace: string; if (__DEV__) { ... } else { parentNamespace = ((hostContext: any): HostContextProd); } const domElement: Instance = createElement( type, props, rootContainerInstance, parentNamespace, ); precacheFiberNode(internalInstanceHandle, domElement); updateFiberProps(domElement, props); return domElement; }createElement
function createElement( type: string, props: Object, rootContainerElement: Element | Document, parentNamespace: string, ): Element { let isCustomComponentTag; const ownerDocument: Document = getOwnerDocumentFromRootContainer( rootContainerElement, ); let domElement: Element; let namespaceURI = parentNamespace; if (namespaceURI === HTML_NAMESPACE) { namespaceURI = getIntrinsicNamespace(type); } if (namespaceURI === HTML_NAMESPACE) { if (type === "script") { const div = ownerDocument.createElement("div"); div.innerHTML = "