摘要:虛擬的初始化在閱讀源碼前,我們先提出一個(gè)問(wèn)題,是如何將虛擬轉(zhuǎn)換為真實(shí)的呢有問(wèn)題以后我們才會(huì)更有目標(biāo)的閱讀代碼,下面我們就帶著這個(gè)問(wèn)題去思考。對(duì)類型的處理主要是更新屬性,然后通過(guò)創(chuàng)建節(jié)點(diǎn),并添加進(jìn)父節(jié)點(diǎn)。最后所有的虛擬都將轉(zhuǎn)為真實(shí)。
前言
本文的主要目的是閱讀源碼的過(guò)程中做下筆記和分享給有需要的小伙伴,可能會(huì)有紕漏和錯(cuò)誤,請(qǐng)讀者自行判斷,頭一次寫閱讀代碼的文章,可能寫得有點(diǎn)亂,有什么問(wèn)題歡迎一起探討一起進(jìn)步。
React的版本為16.4,主分支的代碼,只貼出部分關(guān)鍵代碼,完整代碼請(qǐng)到Github查看。
虛擬DOM的初始化 React.createElement在閱讀源碼前,我們先提出一個(gè)問(wèn)題,React是如何將虛擬DOM轉(zhuǎn)換為真實(shí)的DOM呢?有問(wèn)題以后我們才會(huì)更有目標(biāo)的閱讀代碼,下面我們就帶著這個(gè)問(wèn)題去思考。
在平時(shí)工作中我們常常用JSX語(yǔ)法來(lái)創(chuàng)建React元素實(shí)例,但他們最后都會(huì)通過(guò)打包工具編譯成原生的JS代碼,通過(guò)React.createElement來(lái)創(chuàng)建。例如:
// class ReactComponent extends React.Component { // render() { // returnHello React
; // } // } // 以上代碼會(huì)編譯為: class ReactComponent extends React.Component { render() { React.createElement( "p", { className: "class"}, "Hello React" ) } } //React.createElement(ReactComponent, { someProp: "prop" }, null);
這樣我們就可以創(chuàng)建得到React元素實(shí)例。
先來(lái)看看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,將他取出來(lái) ref = config.ref; } if (hasValidKey(config)) { // 如果有key,將他取出來(lái) 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對(duì)象里 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; } // 添加默認(rèn)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; };
是不是很簡(jiǎn)單呢,主要是把我們傳進(jìn)去的東西組成一個(gè)React元素對(duì)象,而type就是我們傳進(jìn)去的組件類型,他可以是一個(gè)類、函數(shù)或字符串(如"div")。
ReactDom.render雖然我們已經(jīng)得到創(chuàng)建好的React元素,但React有是如何把React元素轉(zhuǎn)換為我們最終想要的DOM呢?就是通過(guò)ReactDom.render函數(shù)啦。
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根容器對(duì)象(只貼部分代碼):
... root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); ...
因代碼過(guò)多只貼出通過(guò)legacyCreateRootFromDOMContainer最終得到的React根容器對(duì)象:
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根容器對(duì)象root后,調(diào)用root.render開(kāi)始虛擬DOM的渲染過(guò)程。
DOMRenderer.unbatchedUpdates(() => { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } });
因代碼量過(guò)大,就不逐一貼出詳細(xì)代碼,只貼出主要的函數(shù)的調(diào)用過(guò)程。
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, |};
按以上函數(shù)調(diào)用過(guò)程,我們來(lái)到beginWork函數(shù),它的作用主要是根據(jù)Fiber對(duì)象的tag來(lái)對(duì)組件進(jìn)行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); } // 根據(jù)組件類型來(lái)進(jìn)行不同處理 switch (workInProgress.tag) { case IndeterminateComponent: // 不確定的組件類型 return mountIndeterminateComponent( current, workInProgress, renderExpirationTime, ); case FunctionalComponent: // 函數(shù)類型的組件 return updateFunctionalComponent(current, workInProgress); case ClassComponent: // 類類型的組件,我們這次主要看這個(gè) 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的作用是對(duì)未初始化的類組件進(jìn)行初始化,對(duì)已經(jīng)初始化的組件更新重用
function updateClassComponent( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { const hasContext = pushLegacyContextProvider(workInProgress); let shouldUpdate; if (current === null) { if (workInProgress.stateNode === null) { // 如果還沒(méi)創(chuàng)建實(shí)例,初始化 constructClassInstance( workInProgress, workInProgress.pendingProps, renderExpirationTime, ); mountClassInstance(workInProgress, renderExpirationTime); shouldUpdate = true; } else { // 如果已經(jīng)創(chuàng)建實(shí)例,則重用實(shí)例 shouldUpdate = resumeMountClassInstance( workInProgress, renderExpirationTime, ); } } else { shouldUpdate = updateClassInstance( current, workInProgress, renderExpirationTime, ); } return finishClassComponent( current, workInProgress, shouldUpdate, hasContext, renderExpirationTime, ); }constructClassInstance
實(shí)例化類組件:
function constructClassInstance( workInProgress: Fiber, props: any, renderExpirationTime: ExpirationTime, ): any { const ctor = workInProgress.type; // 我們傳進(jìn)去的那個(gè)類 const unmaskedContext = getUnmaskedContext(workInProgress); const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyContextObject; const instance = new ctor(props, context); // 創(chuàng)建實(shí)例 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; // 將實(shí)例賦值給stateNode屬性 }mountClassInstance
下面的代碼就有我們熟悉的componentWillMount生命周期出現(xiàn)啦,不過(guò)新版React已經(jīng)不建議使用它。
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新的生命周期函數(shù) 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") ) { // 如果沒(méi)有使用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
調(diào)用組件實(shí)例的render函數(shù)獲取需渲染的子元素,并把子元素進(jìn)行處理為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 { // 調(diào)用render函數(shù)獲取子元素 nextChildren = instance.render(); } } workInProgress.effectTag |= PerformedWork; if (didCaptureError) { reconcileChildrenAtExpirationTime( current, workInProgress, null, renderExpirationTime, ); workInProgress.child = null; } // 把子元素轉(zhuǎn)換為Fiber類型 // 如果子元素?cái)?shù)量大于一(即為數(shù)組)的時(shí)候, // 返回第一個(gè)Fiber類型子元素 reconcileChildrenAtExpirationTime( current, workInProgress, nextChildren, renderExpirationTime, ); // 處理state memoizeState(workInProgress, instance.state); // 處理props memoizeProps(workInProgress, instance.props); if (hasContext) { invalidateContextProvider(workInProgress, true); } // 返回Fiber類型的子元素給beginWork函數(shù), // 一直返回到workLoop函數(shù)(看上面的調(diào)用過(guò)程) return workInProgress.child; }workLoop
我們?cè)倩仡^看下workLoop和performUnitOfWork函數(shù)(看上面的函數(shù)調(diào)用過(guò)程),當(dāng)benginWork對(duì)不同類型組件完成相應(yīng)處理返回子元素后,workLoop繼續(xù)通過(guò)performUnitOfWork來(lái)調(diào)用benginWork對(duì)子元素進(jìn)行處理,從而遍歷虛擬DOM樹(shù):
function workLoop(isYieldy) { if (!isYieldy) { while (nextUnitOfWork !== null) { // 遍歷整棵虛擬DOM樹(shù) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } else { while (nextUnitOfWork !== null && !shouldYield()) { // 遍歷整棵虛擬DOM樹(shù) nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } if (enableProfilerTimer) { pauseActualRenderTimerIfRunning(); } } }performUnitOfWork
這個(gè)函數(shù)很接近把虛擬DOM轉(zhuǎn)換為真實(shí)DOM的代碼啦,當(dāng)遍歷完成一顆虛擬DOM的子樹(shù)后(beginWork返回null,即已沒(méi)有子元素),調(diào)用completeUnitOfWork函數(shù)開(kāi)始轉(zhuǎn)換:
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
此函數(shù)作用為先將當(dāng)前Fiber元素轉(zhuǎn)換為真實(shí)DOM節(jié)點(diǎn),然后在看有無(wú)兄弟節(jié)點(diǎn),若有則返回給上層函數(shù)處理完后再調(diào)用此函數(shù)進(jìn)行轉(zhuǎn)換;否則查看有無(wú)父節(jié)點(diǎn),若有則轉(zhuǎn)換父節(jié)點(diǎn)。
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) { // 調(diào)用completeWork轉(zhuǎn)換虛擬DOM let next = completeWork( current, workInProgress, nextRenderExpirationTime, ); stopWorkTimer(workInProgress); resetExpirationTime(workInProgress, nextRenderExpirationTime); if (next !== null) { stopWorkTimer(workInProgress); return next; } // 處理完當(dāng)前節(jié)點(diǎn)后 if (siblingFiber !== null) { // 如果有兄弟節(jié)點(diǎn),則將其返回 return siblingFiber; } else if (returnFiber !== null) { // 沒(méi)有兄弟節(jié)點(diǎn)而有父節(jié)點(diǎn),則處理父節(jié)點(diǎn) workInProgress = returnFiber; continue; } else { return null; } } else { ... } return null; }completeWork
根據(jù)Fiber的類型進(jìn)行處理和拋出錯(cuò)誤,我們主要看HostComponent類型。對(duì)HostComponent類型的處理主要是更新屬性,然后通過(guò)createInstance創(chuàng)建DOM節(jié)點(diǎn),并添加進(jìn)父節(jié)點(diǎn)。
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 { // 創(chuàng)建并返回DOM元素 let instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); // 將此DOM節(jié)點(diǎn)添加進(jìn)父節(jié)點(diǎn) 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 = "