最近在重讀React的源代碼學習的同時就想寫一個關於React建立更新的系列文章便於更好的記錄學習。本系列是基於React v16.13.1 (March 19, 2020)版本。html
一般是以下圖使用,在提供的 container 裏渲染一個 React 元素,並返回對該組件的引用(或者針對無狀態組件返回 null)。本文主要是將ReactDOM.render的執行流程在後續文章中會對建立更新的細節進行分析,文中的源代碼部分爲了方便閱讀將__DEV__
部分的代碼移除掉了。node
ReactDOM.render(
<App />,
document.getElementById('root')
);
複製代碼
位於:react-dom/src/client/ReactDOMLegacy.js
react
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
// 驗證container是否爲有效的DOM節點
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
複製代碼
返回了一個legacyRenderSubtreeIntoContainer函數,這裏注意有5個參數git
parentComponent
: 父組件由於是初次建立因此爲null。github
children
: 傳入的ReactElementpromise
container
: 渲染React的DOM容器bash
forceHydrate
: 判斷是否須要協調,在服務端渲染的狀況下已渲染的DOM結構是相似的所以能夠在對比後進行復用。在服務端渲染的狀況下使用ReactDOM.hydrate()與 render() 相同只是forceHydrate會標記爲true。dom
callback
: 渲染完成後的回調函數異步
位於:react-dom/src/client/ReactDOMLegacy.js
做用:ide
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
// 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) {
// Initial mount 初次渲染建立FiberRoot
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);
};
}
// Initial mount should not be batched.
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);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
複製代碼
位於:react-dom/src/client/ReactDOMLegacy.js
初次渲染進入建立root的環節:root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)
做用:主要是判斷是否爲服務端渲染,若是是的話就會複用存在的dom節點進行協調(reconciliation)提升性能,若是不是則會清空container中的子元素,最後傳入container和shouldHydrate返回createLegacyRoot函數。
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 判斷是不是服務端渲染
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
複製代碼
位於:react-dom/src/client/ReactDOMRoot.js
做用:返回了一個ReactDOMBlockingRoot實例,這裏傳入了LegacyRoot是一個常量=0表明着如今使用的同步渲染模式,是爲了後續的Concurrent可中斷渲染模式作準備。
export function createLegacyRoot(
container: Container,
options?: RootOptions, // hydrate
): RootType {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
複製代碼
位於:react-dom/src/client/ReactDOMRoot.js
做用:將createRootImpl
函數的返回(FiberRoot)掛載到實例的_internalRoot上
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
this._internalRoot = createRootImpl(container, tag, options);
}
複製代碼
位於:react-dom/src/client/ReactDOMRoot.js
做用:執行createContainer拿到FiberRootNode並賦值給root,再經過markContainerAsRoot將RootFiber掛載到container上。
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;
// 拿到FiberRootNode
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 將FiberRootNode掛載到container
markContainerAsRoot(root.current, container);
if (hydrate && tag !== LegacyRoot) {
const doc =
container.nodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
eagerlyTrapReplayableEvents(container, doc);
}
return root;
}
複製代碼
位於:react-reconciler/src/ReactFiberReconciler.old.js
做用:返回createFiberRoot
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
複製代碼
位於:react-reconciler/src/react-reconciler/src/ReactFiberReconciler.old.js
做用: 新建FiberRoot對象並賦值給root,初始化Fiber(一般叫作RootFiber)經過root.current = uninitializedFiber和uninitializedFiber.stateNode = root將二者聯繫起來。
執行initializeUpdateQueue(uninitializedFiber)
建立一個更新隊列,掛載fiber.updateQueue下面
最後將root返回
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 新建fiberRoot對象
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.
//初始化RootFiber
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
// RootFiber的stateNode指向FiberRoot
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
複製代碼
ReactDOM.render主要建立了三個對象FiberRooat、RootFiber和Updatequeue下面咱們這對這三個對象進行分析
FiberRoot是FiberRootNode(containerInfo, tag, hydrate)的實例
位於:react-reconciler/src/ReactFiberRoot/FiberRootNode
做用:
function FiberRootNode(containerInfo, tag, hydrate) {
// 標記不一樣的組件類型
this.tag = tag;
// 當前應用對應的Fiber對象,是Root Fiber
// current:Fiber對象 對應的是 root 節點,即整個應用根對象
this.current = null;
// root節點,render方法接收的第二個參數
this.containerInfo = containerInfo;
// 只有在持久更新中會用到,也就是不支持增量更新的平臺,react-dom不會用到
this.pendingChildren = null;
this.pingCache = null;
//任務有三種,優先級有高低:
//(1)沒有提交的任務
//(2)沒有提交的被掛起的任務
//(3)沒有提交的可能被掛起的任務
//當前更新對應的過時時間
this.finishedExpirationTime = NoWork;
//已經完成任務的FiberRoot對象,若是你只有一個Root,那麼該對象就是這個Root對應的Fiber或null
//在commit(提交)階段只會處理該值對應的任務
this.finishedWork = null;
// 在任務被掛起的時候經過setTimeout設置的返回內容,用來下一次若是有新的任務掛起時清理還沒觸發的timeout(例如suspense返回的promise)
this.timeoutHandle = noTimeout;
// 頂層context對象,只有主動調用renderSubTreeIntoContainer時纔會被調用
this.context = null;
this.pendingContext = null;
// 第一次渲染是否須要調和
this.hydrate = hydrate;
// Node returned by Scheduler.scheduleCallback
this.callbackNode = null;
this.callbackPriority = NoPriority;
//存在root中,最舊的掛起時間
//不肯定是否掛起的狀態(全部任務一開始均是該狀態)
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
//存在root中,最新的掛起時間
//不肯定是否掛起的狀態(全部任務一開始均是該狀態)
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
this.mutableSourcePendingUpdateTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}
複製代碼
RootFiber初始化於const uninitializedFiber = createHostRootFiber(tag) 經過 createFiber 返回 FiberNode的實例
做用:
// 位於 react-reconciler/src/ReactFiber.js
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;
}
return createFiber(HostRoot, null, null, mode);
}
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
// FiberNode結構
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
// 標記不一樣的組件類型
this.tag = tag;
// ReactElement裏面的key
this.key = key;
// ReactElement.type,也就是咱們調用`createElement`的第一個參數
this.elementType = null;
// 異步組件lazy component resolved以後返回的內容,通常是`function`或者`class`組件
this.type = null;
// 對應節點的實例,好比類組件就是class的實例,若是是dom組件就是dom實例,若是是function component就沒有實例這裏爲空
this.stateNode = null;
// Fiber Fiber是個鏈表經過child和Sibling鏈接,遍歷的時候先遍歷child若是沒有子元素了則訪問return回到上級查詢是否有sibling
// 指向他在Fiber節點樹中的‘parent’,用來在處理完這個節點以後向上返回
this.return = null;
// 指向第一個子節點
this.child = null;
// 指向本身的兄弟節點,兄弟節點的return指向同一個副節點
this.sibling = null;
this.index = 0;
this.ref = null;
// 新的變更帶來的新的props
this.pendingProps = pendingProps;
// 上次渲染完成後的props
this.memoizedProps = null;
// 該Fiber對應的組件產生的update會存放在這個隊列(好比setState和forceUpdate建立的更新)
this.updateQueue = null;
// 上一次的state
this.memoizedState = null;
this.dependencies = null;
//
this.mode = mode;
// Effects
// 用來記錄反作用
this.effectTag = NoEffect;
// 單鏈表用來快速查找下一個side effect
this.nextEffect = null;
// 子樹中第一個side effect
this.firstEffect = null;
// 子樹中最後一個side effect
this.lastEffect = null;
// 表明任務在將來的哪一個時候應該被完成 就是過時時間
// 不包括他的子樹產生的任務
this.expirationTime = NoWork;
// 快速肯定子樹中是否有再也不等待的變化
this.childExpirationTime = NoWork;
// Fiber樹更新過程當中,每一個FIber都會有一個跟其對應的Fiber
// 咱們稱他爲`current <==> workInProgress`
// 渲染完成後他們會交換位置
this.alternate = null;
// 調試相關的去掉了
}
複製代碼
initializeUpdateQueue(uninitializedFiber);
位於:react-reconciler/src/ReactUpdateQueue.js
做用:單向鏈表,用來存放update,next來串聯update
關於Update和UpdateQueue涉及到的東西比較多打算單獨一章來說解
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
// 每次操做完更新阿以後的state
baseState: fiber.memoizedState,
// 隊列中的第一個`Update`
firstBaseUpdate: null,
// 隊列中的最後一個`Update`
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
複製代碼
最後是畫的大體流程圖