React16源碼解讀:揭秘ReactDOM.render
點(diǎn)擊上方"前端之境",選擇"置頂或者星標(biāo)"
你的關(guān)注意義重大!
在上一篇文章中我們通過(guò)create-react-app腳手架快速搭建了一個(gè)簡(jiǎn)單的示例,并基于該示例講解了在類(lèi)組件中React.Component和React.PureComponent背后的實(shí)現(xiàn)原理。同時(shí)我們也了解到,通過(guò)使用 Babel 預(yù)置工具包@babel/preset-react可以將類(lèi)組件中render方法的返回值和函數(shù)定義組件中的返回值轉(zhuǎn)換成使用React.createElement方法包裝而成的多層嵌套結(jié)構(gòu),并基于源碼逐行分析了React.createElement方法背后的實(shí)現(xiàn)過(guò)程和ReactElement構(gòu)造函數(shù)的成員結(jié)構(gòu),最后根據(jù)分析結(jié)果總結(jié)出了幾道面試中可能會(huì)碰到或者自己以前遇到過(guò)的面試考點(diǎn)。上篇文章中的內(nèi)容相對(duì)而言還是比較簡(jiǎn)單基礎(chǔ),主要是為本文以及后續(xù)的任務(wù)調(diào)度相關(guān)內(nèi)容打下基礎(chǔ),幫助我們更好地理解源碼的用意。本文就結(jié)合上篇文章的基礎(chǔ)內(nèi)容,從組件渲染的入口點(diǎn)ReactDOM.render方法開(kāi)始,一步一步深入源碼,揭秘ReactDOM.render方法背后的實(shí)現(xiàn)原理,如有錯(cuò)誤,還請(qǐng)指出。
源碼中有很多判斷類(lèi)似__DEV__變量的控制語(yǔ)句,用于區(qū)分開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境,筆者在閱讀源碼的過(guò)程中不太關(guān)心這些內(nèi)容,就直接略過(guò)了,有興趣的小伙伴兒可以自己研究研究。
render VS hydrate
本系列的源碼分析是基于 Reactv16.10.2版本的,為了保證源碼一致還是建議你選擇相同的版本,下載該版本的地址和筆者選擇該版本的具體原因可以在上一篇文章的準(zhǔn)備階段小節(jié)中查看,這里就不做過(guò)多講解了。項(xiàng)目示例本身也比較簡(jiǎn)單,可以按照準(zhǔn)備階段的步驟自行使用create-react-app快速將一個(gè)簡(jiǎn)單的示例搭建起來(lái),然后我們定位到src/index.js文件下,可以看到如下代碼:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
...
ReactDOM.render(<App />, document.getElementById('root'));
...
該文件即為項(xiàng)目的主入口文件,App組件即為根組件,ReactDOM.render就是我們要開(kāi)始分析源碼的入口點(diǎn)。我們通過(guò)以下路徑可以找到ReactDOM對(duì)象的完整代碼:
packages -> react-dom -> src -> client -> ReactDOM.js
然后我們將代碼定位到第632行,可以看到ReactDOM對(duì)象包含了很多我們可能使用過(guò)的方法,例如render、createPortal、findDOMNode,hydrate和unmountComponentAtNode等。本文中我們暫且只關(guān)心render方法,但為了方便對(duì)比,也可以簡(jiǎn)單看下hydrate方法:
const ReactDOM: Object = {
...
/**
* 服務(wù)端渲染
* @param element 表示一個(gè)ReactNode,可以是一個(gè)ReactElement對(duì)象
* @param container 需要將組件掛載到頁(yè)面中的DOM容器
* @param callback 渲染完成后需要執(zhí)行的回調(diào)函數(shù)
*/
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
...
// TODO: throw or warn if we couldn't hydrate?
// 注意第一個(gè)參數(shù)為null,第四個(gè)參數(shù)為true
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
/**
* 客戶(hù)端渲染
* @param element 表示一個(gè)ReactElement對(duì)象
* @param container 需要將組件掛載到頁(yè)面中的DOM容器
* @param callback 渲染完成后需要執(zhí)行的回調(diào)函數(shù)
*/
render(
element: React$Element,
container: DOMContainer,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
...
// 注意第一個(gè)參數(shù)為null,第四個(gè)參數(shù)為false
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
...
};
發(fā)現(xiàn)沒(méi),render方法的第一個(gè)參數(shù)就是我們?cè)谏掀恼轮兄v過(guò)的ReactElement對(duì)象,所以說(shuō)上篇文章的內(nèi)容就是為了在這里打下基礎(chǔ)的,便于我們對(duì)參數(shù)的理解。事實(shí)上,在源碼中幾乎所有方法參數(shù)中的element字段均可以傳入一個(gè)ReactElement實(shí)例,這個(gè)實(shí)例就是通過(guò) Babel 編譯器在編譯過(guò)程中使用React.createElement方法得到的。接下來(lái)在render方法中調(diào)用legacyRenderSubtreeIntoContainer來(lái)正式進(jìn)入渲染流程,不過(guò)這里需要留意一下的是,render方法和hydrate方法在執(zhí)行legacyRenderSubtreeIntoContainer時(shí),第一個(gè)參數(shù)的值均為null,第四個(gè)參數(shù)的值恰好相反。
然后將代碼定位到第570行,進(jìn)入legacyRenderSubtreeIntoContainer方法的具體實(shí)現(xiàn):
/**
* 開(kāi)始構(gòu)建FiberRoot和RootFiber,之后開(kāi)始執(zhí)行更新任務(wù)
* @param parentComponent 父組件,可以把它當(dāng)成null值來(lái)處理
* @param children ReactDOM.render()或者ReactDOM.hydrate()中的第一個(gè)參數(shù),可以理解為根組件
* @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個(gè)參數(shù),組件需要掛載的DOM容器
* @param forceHydrate 表示是否融合,用于區(qū)分客戶(hù)端渲染和服務(wù)端渲染,render方法傳false,hydrate方法傳true
* @param callback ReactDOM.render()或者ReactDOM.hydrate()中的第三個(gè)參數(shù),組件渲染完成后需要執(zhí)行的回調(diào)函數(shù)
* @returns {*}
*/
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component,
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.
// 在第一次執(zhí)行的時(shí)候,container上是肯定沒(méi)有_reactRootContainer屬性的
// 所以第一次執(zhí)行時(shí),root肯定為undefined
let root: _ReactSyncRoot = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// Initial mount
// 首次掛載,進(jìn)入當(dāng)前流程控制中,container._reactRootContainer指向一個(gè)ReactSyncRoot實(shí)例
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// root表示一個(gè)ReactSyncRoot實(shí)例,實(shí)例中有一個(gè)_internalRoot方法指向一個(gè)fiberRoot實(shí)例
fiberRoot = root._internalRoot;
// callback表示ReactDOM.render()或者ReactDOM.hydrate()中的第三個(gè)參數(shù)
// 重寫(xiě)callback,通過(guò)fiberRoot去找到其對(duì)應(yīng)的rootFiber,然后將rootFiber的第一個(gè)child的stateNode作為callback中的this指向
// 一般情況下我們很少去寫(xiě)第三個(gè)參數(shù),所以可以不必關(guān)心這里的內(nèi)容
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 對(duì)于首次掛載來(lái)說(shuō),更新操作不應(yīng)該是批量的,所以會(huì)先執(zhí)行unbatchedUpdates方法
// 該方法中會(huì)將executionContext(執(zhí)行上下文)切換成LegacyUnbatchedContext(非批量上下文)
// 切換上下文之后再調(diào)用updateContainer執(zhí)行更新操作
// 執(zhí)行完updateContainer之后再將executionContext恢復(fù)到之前的狀態(tài)
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 不是首次掛載,即container._reactRootContainer上已經(jīng)存在一個(gè)ReactSyncRoot實(shí)例
fiberRoot = root._internalRoot;
// 下面的控制語(yǔ)句和上面的邏輯保持一致
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
// 對(duì)于非首次掛載來(lái)說(shuō),是不需要再調(diào)用unbatchedUpdates方法的
// 即不再需要將executionContext(執(zhí)行上下文)切換成LegacyUnbatchedContext(非批量上下文)
// 而是直接調(diào)用updateContainer執(zhí)行更新操作
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
上面代碼的內(nèi)容稍微有些多,咋一看可能不太好理解,我們暫且可以不用著急看完整個(gè)函數(shù)內(nèi)容。試想當(dāng)我們第一次啟動(dòng)運(yùn)行項(xiàng)目的時(shí)候,也就是第一次執(zhí)行ReactDOM.render方法的時(shí)候,這時(shí)去獲取container._reactRootContainer肯定是沒(méi)有值的,所以我們先關(guān)心第一個(gè)if語(yǔ)句中的內(nèi)容:
if (!root) {
// Initial mount
// 首次掛載,進(jìn)入當(dāng)前流程控制中,container._reactRootContainer指向一個(gè)ReactSyncRoot實(shí)例
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
...
}
這里通過(guò)調(diào)用legacyCreateRootFromDOMContainer方法將其返回值賦值給container._reactRootContainer,我們將代碼定位到同文件下的第517行,去看看legacyCreateRootFromDOMContainer的具體實(shí)現(xiàn):
/**
* 創(chuàng)建并返回一個(gè)ReactSyncRoot實(shí)例
* @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個(gè)參數(shù),組件需要掛載的DOM容器
* @param forceHydrate 是否需要強(qiáng)制融合,render方法傳false,hydrate方法傳true
* @returns {ReactSyncRoot}
*/
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): _ReactSyncRoot {
// 判斷是否需要融合
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
// 針對(duì)客戶(hù)端渲染的情況,需要將container容器中的所有元素移除
if (!shouldHydrate) {
let warned = false;
let rootSibling;
// 循環(huán)遍歷每個(gè)子節(jié)點(diǎn)進(jìn)行刪除
while ((rootSibling = container.lastChild)) {
...
container.removeChild(rootSibling);
}
}
...
// Legacy roots are not batched.
// 返回一個(gè)ReactSyncRoot實(shí)例
// 該實(shí)例具有一個(gè)_internalRoot屬性指向fiberRoot
return new ReactSyncRoot(
container,
LegacyRoot,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
/**
* 根據(jù)nodeType和attribute判斷是否需要融合
* @param container DOM容器
* @returns {boolean}
*/
function shouldHydrateDueToLegacyHeuristic(container) {
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
/**
* 根據(jù)container來(lái)獲取DOM容器中的第一個(gè)子節(jié)點(diǎn)
* @param container DOM容器
* @returns {*}
*/
function getReactRootElementInContainer(container: any) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
其中在shouldHydrateDueToLegacyHeuristic方法中,首先根據(jù)container來(lái)獲取 DOM 容器中的第一個(gè)子節(jié)點(diǎn),獲取該子節(jié)點(diǎn)的目的在于通過(guò)節(jié)點(diǎn)的nodeType和是否具有ROOT_ATTRIBUTE_NAME屬性來(lái)區(qū)分是客戶(hù)端渲染還是服務(wù)端渲染,ROOT_ATTRIBUTE_NAME位于packages/react-dom/src/shared/DOMProperty.js文件中,表示data-reactroot屬性。我們知道,在服務(wù)端渲染中有別于客戶(hù)端渲染的是,node服務(wù)會(huì)在后臺(tái)先根據(jù)匹配到的路由生成完整的HTML字符串,然后再將HTML字符串發(fā)送到瀏覽器端,最終生成的HTML結(jié)構(gòu)簡(jiǎn)化后如下:
<body>
<div id="root">
<div data-reactroot="">div>
div>
body>
在客戶(hù)端渲染中是沒(méi)有data-reactroot屬性的,因此就可以區(qū)分出客戶(hù)端渲染和服務(wù)端渲染。在 React 中的nodeType主要包含了五種,其對(duì)應(yīng)的值和W3C中的nodeType標(biāo)準(zhǔn)是保持一致的,位于與DOMProperty.js同級(jí)的HTMLNodeType.js文件中:
// 代表元素節(jié)點(diǎn)
export const ELEMENT_NODE = 1;
// 代表文本節(jié)點(diǎn)
export const TEXT_NODE = 3;
// 代表注釋節(jié)點(diǎn)
export const COMMENT_NODE = 8;
// 代表整個(gè)文檔,即document
export const DOCUMENT_NODE = 9;
// 代表文檔片段節(jié)點(diǎn)
export const DOCUMENT_FRAGMENT_NODE = 11;
經(jīng)過(guò)以上分析,現(xiàn)在我們就可以很容易地區(qū)分出客戶(hù)端渲染和服務(wù)端渲染,并且在面試中如果被問(wèn)到兩種渲染模式的區(qū)別,我們就可以很輕松地在源碼級(jí)別上說(shuō)出兩者的實(shí)現(xiàn)差異,讓面試官眼前一亮。怎么樣,到目前為止,其實(shí)還是覺(jué)得挺簡(jiǎn)單的吧?
FiberRoot VS RootFiber
在這一小節(jié)中,我們將嘗試去理解兩個(gè)比較容易混淆的概念:FiberRoot和RootFiber。這兩個(gè)概念在 React 的整個(gè)任務(wù)調(diào)度過(guò)程中起著關(guān)鍵性的作用,如果不理解這兩個(gè)概念,后續(xù)的任務(wù)調(diào)度過(guò)程就是空談,所以這里也是我們必須要去理解的部分。接下來(lái)接著上一小節(jié)的內(nèi)容,繼續(xù)分析legacyCreateRootFromDOMContainer方法中的剩余內(nèi)容,在函數(shù)體的結(jié)尾返回了一個(gè)ReactSyncRoot實(shí)例,我們重新回到ReactDOM.js文件可以很容易找到ReactSyncRoot構(gòu)造函數(shù)的具體內(nèi)容:
/**
* ReactSyncRoot構(gòu)造函數(shù)
* @param container DOM容器
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @param options 配置信息,只有在hydrate時(shí)才有值,否則為undefined
* @constructor
*/
function ReactSyncRoot(
container: DOMContainer,
tag: RootTag,
options: void | RootOptions,
) {
this._internalRoot = createRootImpl(container, tag, options);
}
/**
* 創(chuàng)建并返回一個(gè)fiberRoot
* @param container DOM容器
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @param options 配置信息,只有在hydrate時(shí)才有值,否則為undefined
* @returns {*}
*/
function createRootImpl(
container: DOMContainer,
tag: RootTag,
options: void | RootOptions,
) {
// Tag is either LegacyRoot or Concurrent Root
// 判斷是否是hydrate模式
const hydrate = options != null && options.hydrate === true;
const hydrationCallbacks =
(options != null && options.hydrationOptions) || null;
// 創(chuàng)建一個(gè)fiberRoot
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 給container附加一個(gè)內(nèi)部屬性用于指向fiberRoot的current屬性對(duì)應(yīng)的rootFiber節(jié)點(diǎn)
markContainerAsRoot(root.current, container);
if (hydrate && tag !== LegacyRoot) {
const doc =
container.nodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
eagerlyTrapReplayableEvents(doc);
}
return root;
}
從上述源碼中,我們可以看到createRootImpl方法通過(guò)調(diào)用createContainer方法來(lái)創(chuàng)建一個(gè)fiberRoot實(shí)例,并將該實(shí)例返回并賦值到ReactSyncRoot構(gòu)造函數(shù)的內(nèi)部成員_internalRoot屬性上。我們繼續(xù)深入createContainer方法去探究一下fiberRoot完整的創(chuàng)建過(guò)程,該方法被抽取到與react-dom包同級(jí)的另一個(gè)相關(guān)的依賴(lài)包react-reconciler包中,然后定位到react-reconciler/src/ReactFiberReconciler.js的第299行:
/**
* 內(nèi)部調(diào)用createFiberRoot方法返回一個(gè)fiberRoot實(shí)例
* @param containerInfo DOM容器
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @param hydrate 判斷是否是hydrate模式
* @param hydrationCallbacks 只有在hydrate模式時(shí)才可能有值,該對(duì)象包含兩個(gè)可選的方法:onHydrated和onDeleted
* @returns {FiberRoot}
*/
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
/**
* 創(chuàng)建fiberRoot和rootFiber并相互引用
* @param containerInfo DOM容器
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @param hydrate 判斷是否是hydrate模式
* @param hydrationCallbacks 只有在hydrate模式時(shí)才可能有值,該對(duì)象包含兩個(gè)可選的方法:onHydrated和onDeleted
* @returns {FiberRoot}
*/
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 通過(guò)FiberRootNode構(gòu)造函數(shù)創(chuàng)建一個(gè)fiberRoot實(shí)例
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
// 通過(guò)createHostRootFiber方法創(chuàng)建fiber tree的根節(jié)點(diǎn),即rootFiber
// 需要留意的是,fiber節(jié)點(diǎn)也會(huì)像DOM樹(shù)結(jié)構(gòu)一樣形成一個(gè)fiber tree單鏈表樹(shù)結(jié)構(gòu)
// 每個(gè)DOM節(jié)點(diǎn)或者組件都會(huì)生成一個(gè)與之對(duì)應(yīng)的fiber節(jié)點(diǎn)(生成的過(guò)程會(huì)在后續(xù)的文章中進(jìn)行解讀)
// 在后續(xù)的調(diào)和(reconciliation)階段起著至關(guān)重要的作用
const uninitializedFiber = createHostRootFiber(tag);
// 創(chuàng)建完rootFiber之后,會(huì)將fiberRoot實(shí)例的current屬性指向剛創(chuàng)建的rootFiber
root.current = uninitializedFiber;
// 同時(shí)rootFiber的stateNode屬性會(huì)指向fiberRoot實(shí)例,形成相互引用
uninitializedFiber.stateNode = root;
// 最后將創(chuàng)建的fiberRoot實(shí)例返回
return root;
}
一個(gè)完整的FiberRootNode實(shí)例包含了很多有用的屬性,這些屬性在任務(wù)調(diào)度階段都發(fā)揮著各自的作用,可以在ReactFiberRoot.js文件中看到完整的FiberRootNode構(gòu)造函數(shù)的實(shí)現(xiàn)(這里只列舉部分屬性):
/**
* FiberRootNode構(gòu)造函數(shù)
* @param containerInfo DOM容器
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @param hydrate 判斷是否是hydrate模式
* @constructor
*/
function FiberRootNode(containerInfo, tag, hydrate) {
// 用于標(biāo)記fiberRoot的類(lèi)型
this.tag = tag;
// 指向當(dāng)前激活的與之對(duì)應(yīng)的rootFiber節(jié)點(diǎn)
this.current = null;
// 和fiberRoot關(guān)聯(lián)的DOM容器的相關(guān)信息
this.containerInfo = containerInfo;
...
// 當(dāng)前的fiberRoot是否處于hydrate模式
this.hydrate = hydrate;
...
// 每個(gè)fiberRoot實(shí)例上都只會(huì)維護(hù)一個(gè)任務(wù),該任務(wù)保存在callbackNode屬性中
this.callbackNode = null;
// 當(dāng)前任務(wù)的優(yōu)先級(jí)
this.callbackPriority = NoPriority;
...
}
部分屬性信息如上所示,由于屬性過(guò)多并且在本文中暫時(shí)還用不到,這里就先不一一列舉出來(lái)了,剩余的屬性及其注釋信息已經(jīng)上傳至Github(https://github.com/qq591468061/react-16.10.2),感興趣的朋友可以自行查看。在了解完了fiberRoot的屬性結(jié)構(gòu)之后,接下來(lái)繼續(xù)探究createFiberRoot方法的后半部分內(nèi)容:
// 以下代碼來(lái)自上文中的createFiberRoot方法
// 通過(guò)createHostRootFiber方法創(chuàng)建fiber tree的根節(jié)點(diǎn),即rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 創(chuàng)建完rootFiber之后,會(huì)將fiberRoot實(shí)例的current屬性指向剛創(chuàng)建的rootFiber
root.current = uninitializedFiber;
// 同時(shí)rootFiber的stateNode屬性會(huì)指向fiberRoot實(shí)例,形成相互引用
uninitializedFiber.stateNode = root;
// 以下代碼來(lái)自ReactFiber.js文件
/**
* 內(nèi)部調(diào)用createFiber方法創(chuàng)建一個(gè)FiberNode實(shí)例
* @param tag fiberRoot節(jié)點(diǎn)的標(biāo)記(LegacyRoot、BatchedRoot、ConcurrentRoot)
* @returns {Fiber}
*/
export function createHostRootFiber(tag: RootTag): Fiber {
let mode;
// 以下代碼根據(jù)fiberRoot的標(biāo)記類(lèi)型來(lái)動(dòng)態(tài)設(shè)置rootFiber的mode屬性
// export const NoMode = 0b0000; => 0
// export const StrictMode = 0b0001; => 1
// export const BatchedMode = 0b0010; => 2
// export const ConcurrentMode = 0b0100; => 4
// export const ProfileMode = 0b1000; => 8
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BatchedMode | StrictMode;
} else if (tag === BatchedRoot) {
mode = BatchedMode | StrictMode;
} else {
mode = NoMode;
}
...
// 調(diào)用createFiber方法創(chuàng)建并返回一個(gè)FiberNode實(shí)例
// HostRoot表示fiber tree的根節(jié)點(diǎn)
// 其他標(biāo)記類(lèi)型可以在shared/ReactWorkTags.js文件中找到
return createFiber(HostRoot, null, null, mode);
}
/**
* 創(chuàng)建并返回一個(gè)FiberNode實(shí)例
* @param tag 用于標(biāo)記fiber節(jié)點(diǎn)的類(lèi)型(所有的類(lèi)型存放在shared/ReactWorkTags.js文件中)
* @param pendingProps 表示待處理的props數(shù)據(jù)
* @param key 用于唯一標(biāo)識(shí)一個(gè)fiber節(jié)點(diǎn)(特別在一些列表數(shù)據(jù)結(jié)構(gòu)中,一般會(huì)要求為每個(gè)DOM節(jié)點(diǎn)或組件加上額外的key屬性,在后續(xù)的調(diào)和階段會(huì)派上用場(chǎng))
* @param mode 表示fiber節(jié)點(diǎn)的模式
* @returns {FiberNode}
*/
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
// FiberNode構(gòu)造函數(shù)用于創(chuàng)建一個(gè)FiberNode實(shí)例,即一個(gè)fiber節(jié)點(diǎn)
return new FiberNode(tag, pendingProps, key, mode);
};
至此我們就成功地創(chuàng)建了一個(gè)fiber節(jié)點(diǎn),上文中我們提到過(guò),和 DOM 樹(shù)結(jié)構(gòu)類(lèi)似,fiber節(jié)點(diǎn)也會(huì)形成一個(gè)與 DOM 樹(shù)結(jié)構(gòu)對(duì)應(yīng)的fiber tree,并且是基于單鏈表的樹(shù)結(jié)構(gòu),我們?cè)谏厦鎰倓?chuàng)建的fiber節(jié)點(diǎn)可作為整個(gè)fiber tree的根節(jié)點(diǎn),即RootFiber節(jié)點(diǎn)。在目前階段,我們暫時(shí)不用關(guān)心一個(gè)fiber節(jié)點(diǎn)所包含的所有屬性,但可以稍微留意一下以下相關(guān)屬性:
/**
* FiberNode構(gòu)造函數(shù)
* @param tag 用于標(biāo)記fiber節(jié)點(diǎn)的類(lèi)型
* @param pendingProps 表示待處理的props數(shù)據(jù)
* @param key 用于唯一標(biāo)識(shí)一個(gè)fiber節(jié)點(diǎn)(特別在一些列表數(shù)據(jù)結(jié)構(gòu)中,一般會(huì)要求為每個(gè)DOM節(jié)點(diǎn)或組件加上額外的key屬性,在后續(xù)的調(diào)和階段會(huì)派上用場(chǎng))
* @param mode 表示fiber節(jié)點(diǎn)的模式
* @constructor
*/
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
// 用于標(biāo)記fiber節(jié)點(diǎn)的類(lèi)型
this.tag = tag;
// 用于唯一標(biāo)識(shí)一個(gè)fiber節(jié)點(diǎn)
this.key = key;
...
// 對(duì)于rootFiber節(jié)點(diǎn)而言,stateNode屬性指向?qū)?yīng)的fiberRoot節(jié)點(diǎn)
// 對(duì)于child fiber節(jié)點(diǎn)而言,stateNode屬性指向?qū)?yīng)的組件實(shí)例
this.stateNode = null;
// Fiber
// 以下屬性創(chuàng)建單鏈表樹(shù)結(jié)構(gòu)
// return屬性始終指向父節(jié)點(diǎn)
// child屬性始終指向第一個(gè)子節(jié)點(diǎn)
// sibling屬性始終指向第一個(gè)兄弟節(jié)點(diǎn)
this.return = null;
this.child = null;
this.sibling = null;
// index屬性表示當(dāng)前fiber節(jié)點(diǎn)的索引
this.index = 0;
...
// 表示待處理的props數(shù)據(jù)
this.pendingProps = pendingProps;
// 表示之前已經(jīng)存儲(chǔ)的props數(shù)據(jù)
this.memoizedProps = null;
// 表示更新隊(duì)列
// 例如在常見(jiàn)的setState操作中
// 其實(shí)會(huì)先將需要更新的數(shù)據(jù)存放到這里的updateQueue隊(duì)列中用于后續(xù)調(diào)度
this.updateQueue = null;
// 表示之前已經(jīng)存儲(chǔ)的state數(shù)據(jù)
this.memoizedState = null;
...
// 表示fiber節(jié)點(diǎn)的模式
this.mode = mode;
// 表示當(dāng)前更新任務(wù)的過(guò)期時(shí)間,即在該時(shí)間之后更新任務(wù)將會(huì)被完成
this.expirationTime = NoWork;
// 表示當(dāng)前fiber節(jié)點(diǎn)的子fiber節(jié)點(diǎn)中具有最高優(yōu)先級(jí)的任務(wù)的過(guò)期時(shí)間
// 該屬性的值會(huì)根據(jù)子fiber節(jié)點(diǎn)中的任務(wù)優(yōu)先級(jí)進(jìn)行動(dòng)態(tài)調(diào)整
this.childExpirationTime = NoWork;
// 用于指向另一個(gè)fiber節(jié)點(diǎn)
// 這兩個(gè)fiber節(jié)點(diǎn)使用alternate屬性相互引用,形成雙緩沖
// alternate屬性指向的fiber節(jié)點(diǎn)在任務(wù)調(diào)度中又稱(chēng)為workInProgress節(jié)點(diǎn)
this.alternate = null;
...
}
其他有用的屬性筆者已經(jīng)在源碼中寫(xiě)好相關(guān)注釋?zhuān)信d趣的朋友可以在Github上查看完整的注釋信息幫助理解。當(dāng)然在現(xiàn)階段,其中的一些屬性還暫時(shí)難以理解,不過(guò)沒(méi)有關(guān)系,在后續(xù)的內(nèi)容和系列文章中將會(huì)逐個(gè)擊破。在本小節(jié)中我們主要是為了理解FiberRoot和RootFiber這兩個(gè)容易混淆的概念以及兩者之間的聯(lián)系。同時(shí)在這里我們需要特別注意的是,多個(gè)fiber節(jié)點(diǎn)可形成基于單鏈表的樹(shù)形結(jié)構(gòu),通過(guò)自身的return,child和sibling屬性可以在多個(gè)fiber節(jié)點(diǎn)之間建立聯(lián)系。為了更加容易理解多個(gè)fiber節(jié)點(diǎn)及其屬性之間的關(guān)系,這里先回顧一下在上一篇文章中的簡(jiǎn)單示例,我們?cè)?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(48,126,245);">src/App.js文件中將create-react-app腳手架生成的默認(rèn)根組件App修改為如下形式:
import React, {Component} from 'react';
function List({data}) {
return (
<ul className="data-list">
{
data.map(item => {
return <li className="data-item" key={item}>{item}li>
})
}
ul>
);
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [1, 2, 3]
};
}
render() {
return (
<div className="container">
<h1 className="title">React learningh1>
<List data={this.state.data} />
div>
);
}
}
最終生成的 DOM 結(jié)構(gòu)如下所示:
class="container">
<h1 class="title">React learningh1>
<ul class="data-list">
<li class="data-item">1li>
<li class="data-item">2li>
<li class="data-item">3li>
ul>
div>
基于該 DOM 結(jié)構(gòu)再結(jié)合上文中對(duì)源碼的分析過(guò)程,最后我們可以嘗試得出一張關(guān)系圖來(lái)加深印象:
總結(jié)
本文主要是在上一篇文章內(nèi)容的基礎(chǔ)之上從零開(kāi)始逐行分析ReactDOM.render方法的實(shí)現(xiàn)原理,其背后的實(shí)現(xiàn)過(guò)程和調(diào)用棧還是非常復(fù)雜的,自己也是處于不斷的摸索過(guò)程中。在本文中主要是介紹兩個(gè)核心概念:FiberRoot和RootFiber,只有理解并區(qū)分這兩個(gè)概念之后才能更好地理解 React 的Fiber架構(gòu)和任務(wù)調(diào)度階段中任務(wù)的執(zhí)行過(guò)程。閱讀源碼的過(guò)程是痛苦的,但與此同時(shí)自己所獲得的收益也是巨大的,為了避免文章過(guò)于枯燥,還是打算將源碼內(nèi)容劃分到系列文章中來(lái)單獨(dú)解讀,期間的間隔時(shí)間可用于對(duì)之前內(nèi)容進(jìn)行回顧,避免一口吃個(gè)胖子反而效果不好。
感謝閱讀
如果你覺(jué)得這篇文章的內(nèi)容對(duì)你有幫助,我想邀請(qǐng)你幫我兩個(gè)小忙:
- 點(diǎn)個(gè)「在看」,你的在看對(duì)我而言是莫大的鼓勵(lì),也讓更多的人能看到這篇內(nèi)容。
- 關(guān)注公眾號(hào)「前端之境」,可以邀你入群,我們一起互相學(xué)習(xí),共同進(jìn)步。
