React源码学习笔记---render流程(1)

136 阅读2分钟

ReactDOM.render

    render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    // 注意下 forceHydrate 参数(也就是第四个参数),为 true 时是服务端渲染
    // 调用 render 函数的话这个值永远为 false,调用 hydrate 函数的话这个值会为 true
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  }

接下来是legacyRenderSubtreeIntoContainer

没有root节点,创建一个新的

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {

  // 一开始进来 container 上是肯定没有这个属性的
  let root: Root = (container._reactRootContainer: any);
  // 没有 root 会执行 if 中的操作
  if (!root) {
    // Initial mount
    // 创建一个 root 出来,类型是 ReactRoot,并且挂载到container._reactRootContainer上
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
  }
}

接下来是legacyCreateRootFromDOMContainer

返回ReactRoot的实例

    function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): Root {
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // forceHydrate为false,所以进if的判断
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    // container 内部如果有元素的话,就全部清掉
    // 但是一般来说我们都是这样写 container 的: <div id='root'></div>
    // 所以说 container 内部不要写任何的节点,一是会被清掉,二是还要进行 DOM 操作,可能还会涉及到重绘回流等等
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }
  // Legacy roots are not async by default.
  // 对于 Root 来说不需要异步
  const isConcurrent = false;
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

ReactRoot

返回创建FiberRoot方法,并且ReactRoot实例通过_internalRoot关联

function ReactRoot(
  container: DOMContainer,
  isConcurrent: boolean,
  hydrate: boolean,
) {
  // 这个 root 指的是 FiberRoot
  const root = createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}

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

createFiberRoot

创建FiberRoot节点和RootFiber,他们之间相互引用,对于 FiberRoot 对象来说,我们现在只需要了解两个属性,分别是 containerInfo 及 current。前者代表着容器信息,也就是我们的 document.querySelector('#root');后者指向 RootFiber

对于 RootFiber 对象来说,我们需要了解的属性稍微多点.return、child、sibling 这三个属性很重要,它们是构成 fiber 树的主体数据结构。fiber 树其实是一个单链表树结构,return 及 child 分别对应着树的父子节点,并且父节点只有一个 child 指向它的第一个子节点,即便是父节点有好多个子节点。那么多个子节点如何连接起来呢?答案是 sibling,每个子节点都有一个 sibling 属性指向着下一个子节点,都有一个 return 属性指向着父节点。

最后是 alternate 属性。其实在一个 React 应用中,通常来说都有两个 fiebr 树,一个叫做 old tree,另一个叫做 workInProgress tree。前者对应着已经渲染好的 DOM 树,后者是正在执行更新中的 fiber tree,还能便于中断后恢复。两棵树的节点互相引用,便于共享一些内部的属性,减少内存的开销(double buffering)。

function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  // FiberRootNode 内部创建了很多属性
  const root: FiberRoot = (new FiberRootNode(containerInfo, hydrate): any);

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  // 创建一个 root fiber,这也是 React 16 中的核心架构了
  // fiber 其实也会组成一个树结构,内部使用了单链表树结构,每个节点及组件都会对应一个 fiber
  // FiberRoot 和 Root Fiber 会互相引用

  // 另外如果你有 React 写的项目的话,可以通过以下代码找到 Fiber Root,它对应着容器
  // document.querySelector('#root')._reactRootContainer._internalRoot
  const uninitializedFiber = createHostRootFiber(isConcurrent);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}

Fiber树图解