首次渲染

渲染入口

将应用渲染至页面:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'; // 应用根组件

ReactDOM.render(<App />, document.getElementById('root')); // 应用挂载容器DOM

看 ReactDOM.render源码——packages/react-dom/src/client/ReactDOM.js,有三个类似的方法,最后都是返回出legacyRenderSubtreeIntoContainer

const ReactDOM: Object = {
  // 新API,未来代替render
  hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
    // TODO: throw or warn if we couldn't hydrate?
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      true,
      callback,
    );
  },
  // React15的重要API,逐渐退出舞台
  render(
    element: React$Element<any>,  // react组件对象,通常是项目根组件
    container: DOMContainer, // id为root的那个dom
    callback: ?Function, // 回调函数
  ) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },
  // 将组件挂载到传入的 DOM 节点上(不稳定api)
  unstable_renderSubtreeIntoContainer(
    parentComponent: React$Component<any, any>,
    element: React$Element<any>,
    containerNode: DOMContainer,
    callback: ?Function,
  ) {
    invariant(
      parentComponent != null && ReactInstanceMap.has(parentComponent),
      'parentComponent must be a valid React Component',
    );
    return legacyRenderSubtreeIntoContainer(
      parentComponent,
      element,
      containerNode,
      false,
      callback,
    );
  },
};

渲染虚拟dom树

legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: _ReactSyncRoot = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    // Initial mount
    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);
}

由此可见,legacyRenderSubtreeIntoContainer主要执行了以下几个操作:

  • root:由legacyCreateRootFromDOMContainer生成,该函数会生成一个FiberRoot对象挂载到真实的dom根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上。
  • unbatchedUpdates:unbatchedUpdates的回调执行updateContainer。
  • updateContainer

    legacyCreateRootFromDOMContainer

    function legacyCreateRootFromDOMContainer(
    container: DOMContainer,
    forceHydrate: boolean,
    ): _ReactSyncRoot {
    const shouldHydrate =
      forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    if (!shouldHydrate) {
      let warned = false;
      let rootSibling;
      while ((rootSibling = container.lastChild)) {
        if (__DEV__) {
          if (
            !warned &&
            rootSibling.nodeType === ELEMENT_NODE &&
            (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
          ) {
            warned = true;
            warningWithoutStack(
              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 (__DEV__) {
      if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
        warnedAboutHydrateAPI = true;
        lowPriorityWarning(
          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.',
        );
      }
    }
    
    // Legacy roots are not batched.
    return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
    }
    

    我们发现该函数实际上返回的是由构造函数ReactSyncRoot创建的对象

ReactSyncRoot

function ReactSyncRoot(
  container: DOMContainer,
  tag: RootTag,
  hydrate: boolean,
) {
  // Tag is either LegacyRoot or Concurrent Root
  const root = createContainer(container, tag, hydrate);
  this._internalRoot = root;
}
ReactRoot.prototype.render = ReactSyncRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  if (__DEV__) {
    warnOnInvalidCallback(callback, 'render');
  }
  if (callback !== null) {
    work.then(callback);
  }
  updateContainer(children, root, null, work._onCommit);
  return work;
};

ReactRoot.prototype.unmount = ReactSyncRoot.prototype.unmount = function(
  callback: ?() => mixed,
): Work {
  ...
};

// Sync roots cannot create batches. Only concurrent ones.
ReactRoot.prototype.createBatch = function(): Batch {
  ...
};

可以看出构造函数ReactSyncRoot有render、unmount等原型方法外,同时还声明了一个和fiber相关的_internalRoot属性。其中render原型方法会去执行updateContainer方法更新容器内容。_internalRoot是由createContainer生成的。我们找到createContainer,源码在packages\react-reconciler\src\ReactFiberReconciler.js中:

createContainer

export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
): OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate);
}

接下来我们看看createFiberRoot是怎么将一个真实DOM变成一个Fiber对象,我们找到createFiberRoot,源码在 packages\react-reconciler\src\ReactFiberRoot.js 中:

createFiberRoot

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
): FiberRoot {
//
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any. // 创建初始根组件对应的fiber实例
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}
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.firstBatch = null;
  this.callbackNode = null;
  this.callbackExpirationTime = NoWork;
  this.firstPendingTime = NoWork;
  this.lastPendingTime = NoWork;
  this.pingTime = NoWork;

  if (enableSchedulerTracing) {
    this.interactionThreadID = unstable_getThreadID();
    this.memoizedInteractions = new Set();
    this.pendingInteractionMap = new Map();
  }
}

// 返回一个初始根组件对应的fiber实例
export function createHostRootFiber(tag: RootTag): Fiber {
  let mode;
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode | BatchedMode | StrictMode;
  } else if (tag === BatchedRoot) {
    mode = BatchedMode | StrictMode;
  } else {
    mode = NoMode;
  }

  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;
  }

// 创建 Fiber 实例 
// HostRoot:组件树根组件,可以嵌套
  return createFiber(HostRoot, null, null, mode);
}

// 创建 Fiber 实例
const createFiber = function(
  tag: WorkTag, // 标记 fiber 类型
  pendingProps: mixed, // 当前处理过程中的组件props对象
  key: null | string, // 调和阶段,标识fiber,以检测是否可重用该fiber实例
  mode: TypeOfMode,
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  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.firstContextDependency = 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) {
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }

  if (__DEV__) {
    this._debugID = debugCounter++;
    this._debugSource = null;
    this._debugOwner = null;
    this._debugIsCurrentlyTiming = false;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this);
    }
  }
}

由此可知,react-dom渲染模块调用createContainer创建容器、根fiber实例、FiberRoot对象等。所有Fiber对象都是FiberNode的实例,它有许多种类型,通过tag来标识,其中内部有很多方法来生成Fiber对象:

  • createFiberFromElement:type为类,无状态函数,元素标签名
  • createFiberFromFragment:type为React.Fragment
  • createFiberFromText:在JSX中表现为字符串,数字
  • createFiberFromPortal:用于 createPortal
  • createFiberRoot:用于ReactDOM.render的根节点

这里createFiberRoot就是创建了一个普通对象,里面current属性引用fiber对象,containerInfo属性引用ReactDOM.render(<div/>, container)的第二个参数,也就是一个元素节点,然后fiber对象的stateNode引用普通对象root。在React15中,stateNode应该是一个组件实例或真实DOM,最后返回普通对象stateNode。现在我们回顾下调用reactDOM.render传入的container,在执行过程中附加了哪些有用的东西:

container = { // 就是我们传入的那个真实dom
  _reactRootContainer: { // legacyCreateRootFromDOMContainer
    _internalRoot: { // DOMRenderer.createContainer
      current:{}  // new FiberNode
    }
  }
}
react16.8.6
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里只属于前端工程师。