本文基于 v16.12.0 版本
ReactDOM.render
function render(element, container, callback) {
if (!isValidContainer(container)) {
{
throw Error("Target container is not a DOM element.");
}
}
{
var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;
if (isModernRoot) {
warningWithoutStack$1(false, '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)?');
}
}
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
ReactDOM.render 调用了 legacyRenderSubtreeIntoContainer,这是一个内部API:
legacyRenderSubtreeIntoContainer
从字面可以看出它大致意思就是把虚拟的dom树渲染到真实的dom容器中
// 将子树渲染到容器中
/**
* 开始构建FiberRoot和RootFiber,之后开始执行更新任务
* @param parentComponent 父组件,可以把它当成null值来处理
* @param children ReactDOM.render()或者ReactDOM.hydrate()中的第一个参数,可以理解为根组件
* @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二个参数,组件需要挂载的DOM容器
* @param forceHydrate 表示是否融合,用于区分客户端渲染和服务端渲染,render方法传false,hydrate方法传true
* @param callback ReactDOM.render()或者ReactDOM.hydrate()中的第三个参数,组件渲染完成后需要执行的回调函数
* @returns {*}
*/
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
{
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}
var root = container._reactRootContainer;
var fiberRoot;
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
var originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
var _originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
_originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
源码说明
legacyRenderSubtreeIntoContainer 主要执行了以下几个操作:
- root = container._reactRootContainer:如不存在,表示是 Initial mount 阶段,调用 legacyCreateRootFromDOMContainer 生成;如存在,表示是 update 阶段;
- fiberRoot = root._internalRoot:从 root 上拿到内部 _internalRoot 属性;
- 封装 callback 回调(通过 fiberRoot 找到其对应的 rootFiber,然后将 rootFiber.child.stateNode 作为 callback 中的 this 指向,调用 callback);
- mount 阶段,需要尽快完成,不允许批量更新,使用 unbatchedUpdates 调用 updateContainer();update 阶段,直接调用 updateContainer() 执行更新;
- 返回getPublicRootInstance(fiberRoot):返回公开的 Root 实例对象。
legacyCreateRootFromDOMContainer
// 从 DOMContainer 创建 Root
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
// 当不需要“融合”时,清空 container
if (!shouldHydrate) {
var warned = false;
var rootSibling;
while (rootSibling = container.lastChild) {
{
if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
warned = true;
warningWithoutStack$1(false, '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 (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
lowPriorityWarningWithoutStack$1(false, 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v17. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.');
}
}
return createLegacyRoot(container, shouldHydrate ? {
hydrate: true
} : undefined);
}
源码说明
- 首先,判断是否需要“融合”;
- 如果不需要,直接删掉 container 的所有子元素
- 如果子元素上有 “data-reactroot” 属性,打印 error;
- 开发模式下,调用 ReactDOM.render() 执行“融合”时,会提示警告,应使用 ReactDOM.hydrate();
- 使用 createLegacyRoot 创建 ReactDOMBlockingRoot 实例并返回(该实例上只有一个 _internalRoot 属性指向 FiberRootNode 实例)。
shouldHydrateDueToLegacyHeuristic
export const ROOT_ATTRIBUTE_NAME = 'data-reactroot';
// 由于遗留的启发式需要“融合”
function shouldHydrateDueToLegacyHeuristic(container) {
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
源码说明
判断 container 的第一个子元素上是否存在 'data-reactroot' 属性,如果存在,表示需要进行“融合”。
getReactRootElementInContainer
function getReactRootElementInContainer(container) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
源码说明
如果 container 是 document 则返回<html>
,否则返回它的第一个子节点。
看到这里就需要注意我们为什么不推荐使用 document 来作为 container 了,因为他会直接把
<html>
覆盖。
document.documentElement
对于任何非空 HTML 文档,调用 document.documentElement 总是会返回一个 <html>
元素,且它一定是该文档的根元素。借助这个只读属性,能方便地获取到任意文档的根元素。
HTML 文档通常包含一个子节点 ,但在它前面可能还有个 DOCTYPE 声明。XML 文档通常包含多个子节点:根元素,DOCTYPE 声明,和 processing instructions。
所以,应当使用 document.documentElement 来获取根元素, 而不是 document.firstChild。
createLegacyRoot
var LegacyRoot = 0;
var BlockingRoot = 1;
var ConcurrentRoot = 2;
//回顾一下传参:
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
//...
return createLegacyRoot(container, shouldHydrate ? {
hydrate: true
} : undefined);
}
function createLegacyRoot(container, options) {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
源码说明
调用 ReactDOMBlockingRoot 类,生成一个 FiberRootNode 对象,通过 LegacyRoot 区分 Root 类型,options 为是否需要“融合”。
该函数会生成一个 ReactSyncRoot 对象挂载到真实的 dom 根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上;
ReactDOMBlockingRoot
function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}
ReactDOMBlockingRoot 的构造函数中,只有一个 _internalRoot 属性,其值由 createRootImpl 函数创建。
createRootImpl
/**
* 创建并返回一个fiberRoot
* @param container DOM容器
* @param tag fiberRoot节点的标记(LegacyRoot、BlockingRoot、ConcurrentRoot)
* @param options 配置信息,只有在需要“融合”时才有值,否则为 undefined
* @returns {*}
*/
function createRootImpl(container, tag, options) {
var hydrate = options != null && options.hydrate === true;
var hydrationCallbacks = options != null && options.hydrationOptions || null;
var root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
if (hydrate && tag !== LegacyRoot) {
var doc = container.nodeType === DOCUMENT_NODE ? container : container.ownerDocument;
eagerlyTrapReplayableEvents(doc);
}
return root;
}
源码说明
- 根据传参判断是否需要“融合”;
- 调用 createContainer,createContainer 只是一层壳,实质是调用 createFiberRoot 生成一个 FiberRootNode 实例(该实例的 current 属性指向一个新创建的 FiberNode 实例,并将此 FiberNode 实例的 stateNode 指向 FiberRootNode,形成一个回链);
- 调用 markContainerAsRoot,给 container 添加一个内部属性
__reactContainere$+随机key
用于指向 FiberNode 节点; - 返回 FiberRootNode 对象。
createContainer
// 回顾一下调用栈:
// legacyRenderSubtreeIntoContainer > legacyCreateRootFromDOMContainer >
// > createLegacyRoot > new ReactDOMBlockingRoot > createRootImpl > createContainer
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
createFiberRoot
var enableSuspenseCallback = false;
// Part of the simplification of React.createElement so we can eventually move from React.createElement to React.jsx
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
var root = new FiberRootNode(containerInfo, tag, hydrate);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// Cyclic construction. This cheats the type system right now because stateNode is any.
// 循环结构
var uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
return root;
}
源码说明
- 创建 FiberRootNode 对象,设置其 tag 属性为 LegacyRoot、hydrate 属性为前面判断出的是否需要融合的布尔值;
- 创建 tag 类型为 HostRoot 的 FiberNode 对象(即 fiber tree 的根节点,rootFiber),并根据 FiberRootNode 实例的 tag 计算结果设置 FiberNode 的 mode;
- 将 FiberRootNode 的 current 属性指向 FiberNode,FiberNode 的 stateNode 指向 FiberRootNode,形成循环结构。
FiberRootNode
var enableSchedulerTracing = true; // SSR experiments
var enableSuspenseCallback = false; // Part of the simplification of React.createElement so we can eventually move from React.createElement to React.jsx
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.current = null;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
this.callbackPriority = NoPriority;
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = tracing.unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}
createHostRootFiber
var LegacyRoot = 0;
var BlockingRoot = 1;
var ConcurrentRoot = 2;
var NoMode = 0;
var StrictMode = 1;
// TODO: Remove BlockingMode and ConcurrentMode by reading from the root tag instead
var BlockingMode = 2;
var ConcurrentMode = 4;
var ProfileMode = 8;
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
//react 树的根。可以嵌套在另一个节点内。
var enableProfilerTimer = true; // Trace which interactions trigger each commit.
var isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
function createHostRootFiber(tag) {
var mode;
// 根据 fiberRoot 的 tag 类型设置 rootFiber 的 mode 属性
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BlockingMode | StrictMode; //7
} else if (tag === BlockingRoot) {
mode = BlockingMode | StrictMode; //3
} else {
mode = NoMode; //0
}
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;
}
// HostRoot 表示 fiber tree的根节点
return createFiber(HostRoot, null, null, mode);
}
源码说明
- 根据 fiberRoot 的 tag 属性设置 rootFiber 的 mode 属性,fiberRoot 的 tag 是 LegacyRoot,所以 mode 为 NoMode;
- 如果存在 DevTools,将 mode 再 |8;
- 调用 createFiber 创建 FiberNode 对象,设置其 tag 为 HostRoot(即3),mode 为 计算好的 mode。
createFiber
var createFiber = function (tag, pendingProps, key, mode) {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
return new FiberNode(tag, pendingProps, key, mode);
};
FiberNode
FiberNode 构造函数用于创建一个 FiberNode 实例,即一个 fiber 节点。
多个fiber节点可形成基于单链表的树形结构,通过自身的return,child和sibling属性可以在多个fiber节点之间建立联系。
var enableProfilerTimer = true; // Trace which interactions trigger each commit.
var enableUserTimingAPI = true; // Helps identify side effects in render-phase lifecycle hooks and setState reducers by double invoking them in Strict Mode.
var hasBadMapPolyfill;
{
hasBadMapPolyfill = false;
try {
var nonExtensibleObject = Object.preventExtensions({});
var testMap = new Map([[nonExtensibleObject, null]]);
var testSet = new Set([nonExtensibleObject]);
testMap.set(0, 0);
testSet.add(0);
} catch (e) {
// TODO: Consider warning about bad polyfills
hasBadMapPolyfill = true;
}
}
var debugCounter = 1;
function FiberNode(tag, pendingProps, key, mode) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = NoWork;
this.childExpirationTime = NoWork;
this.alternate = null;
if (enableProfilerTimer) {
// Note: The following is done to avoid a v8 performance cliff.
// 注意:这样做是为了避免v8性能下降。
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
if (enableUserTimingAPI) {
this._debugID = debugCounter++;
this._debugIsCurrentlyTiming = false;
}
{
this._debugSource = null;
this._debugOwner = null;
this._debugNeedsRemount = false;
this._debugHookTypes = null;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
}
}
}
markContainerAsRoot
// 回顾一下调用栈和传参:
// legacyRenderSubtreeIntoContainer > legacyCreateRootFromDOMContainer >
// > createLegacyRoot > new ReactDOMBlockingRoot > createRootImpl > markContainerAsRoot
function createRootImpl(container, tag, options) {
//...
// root 即创建的 FiberRootNode 实例,其 current 属性即 FiberNode 实例
var root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
//...
return root;
}
var randomKey = Math.random().toString(36).slice(2);
var internalInstanceKey = '__reactInternalInstance$' + randomKey;
var internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
var internalContainerInstanceKey = '__reactContainere$' + randomKey;
function precacheFiberNode(hostInst, node) {
node[internalInstanceKey] = hostInst;
}
function markContainerAsRoot(hostRoot, node) {
node[internalContainerInstanceKey] = hostRoot;
}
function unmarkContainerAsRoot(node) {
node[internalContainerInstanceKey] = null;
}
function isContainerMarkedAsRoot(node) {
return !!node[internalContainerInstanceKey];
}
源码说明
- 如上,markContainerAsRoot 传参中的 hostRoot 即 FiberNode 实例(即,
container._reactRootContainer._internalRoot.current
),node 即 container。 - 所以,markContainerAsRoot 的作用是:给 container 添加一个内部属性
__reactContainere$+随机key
用于指向此 FiberNode 节点。
eagerlyTrapReplayableEvents
此函数涉及到 react 中的事件机制,放到后面章节详解。
unbatchedUpdates & updateContainer
涉及到 Scheduler 调度,放到下一章详解。
getPublicRootInstance
// 回顾一下调用栈和传参:
// legacyRenderSubtreeIntoContainer > getPublicRootInstance
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
//...
return getPublicRootInstance(fiberRoot);
}
//获取root实例
function getPublicRootInstance(container) {
//获取当前fiber节点
var containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
}
源码说明
- 获取当前 fiber 节点,即 rootFiber;
- 由于是 Initial mount 阶段,rootFiber 还没有子节点,所以返回 null;
- 其他情况,返回 containerFiber.child.stateNode。
getPublicInstance
function getPublicInstance(instance) {
return instance;
}