react-dom 源码(2)render

1,848 阅读3分钟

本文基于 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 主要执行了以下几个操作:

  1. root = container._reactRootContainer:如不存在,表示是 Initial mount 阶段,调用 legacyCreateRootFromDOMContainer 生成;如存在,表示是 update 阶段;
  2. fiberRoot = root._internalRoot:从 root 上拿到内部 _internalRoot 属性;
  3. 封装 callback 回调(通过 fiberRoot 找到其对应的 rootFiber,然后将 rootFiber.child.stateNode 作为 callback 中的 this 指向,调用 callback);
  4. mount 阶段,需要尽快完成,不允许批量更新,使用 unbatchedUpdates 调用 updateContainer();update 阶段,直接调用 updateContainer() 执行更新;
  5. 返回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);
}

源码说明

  1. 首先,判断是否需要“融合”;
  2. 如果不需要,直接删掉 container 的所有子元素
    • 如果子元素上有 “data-reactroot” 属性,打印 error;
    • 开发模式下,调用 ReactDOM.render() 执行“融合”时,会提示警告,应使用 ReactDOM.hydrate();
  3. 使用 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;
}

源码说明

  1. 根据传参判断是否需要“融合”;
  2. 调用 createContainer,createContainer 只是一层壳,实质是调用 createFiberRoot 生成一个 FiberRootNode 实例(该实例的 current 属性指向一个新创建的 FiberNode 实例,并将此 FiberNode 实例的 stateNode 指向 FiberRootNode,形成一个回链)
  3. 调用 markContainerAsRoot,给 container 添加一个内部属性 __reactContainere$+随机key 用于指向 FiberNode 节点;
  4. 返回 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);
}

源码说明

  1. 根据 fiberRoot 的 tag 属性设置 rootFiber 的 mode 属性,fiberRoot 的 tag 是 LegacyRoot,所以 mode 为 NoMode;
  2. 如果存在 DevTools,将 mode 再 |8;
  3. 调用 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;
  }
}

源码说明

  1. 获取当前 fiber 节点,即 rootFiber;
  2. 由于是 Initial mount 阶段,rootFiber 还没有子节点,所以返回 null;
  3. 其他情况,返回 containerFiber.child.stateNode。

getPublicInstance

function getPublicInstance(instance) {
  return instance;
}