React 源码系列:细说 ReactDOM.render()

1,078 阅读2分钟

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

前一篇文章已经梳理了源码的阅读顺序,我们顺着 render -> legacyRenderSubtreeIntoContainer -> updateContainer 这个链路,更为详细地分析一下代码的功能。

以下代码分析过程的某些描述可能与你对代码的理解有出入,这是因为为了简化对原理的理解,本文省略了某些实现的细节,真实的实现会比本文描述更复杂。比如说,某处构造 root 是调了createContainer函数,但其实该函数只是对createFiberRoot函数的封装,本文会直接说某处构造 root 调了 createFiberRoot

render

看一下 render函数的签名

function render (
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  // ...
}

调用方式

ReactDOM.render(
  <React.StrictMode>
      <App></App>
  </React.StrictMode>,
  document.getElementById('root')
)

element 就是我们写的 React 组件,container 是渲染根组件的容器,一般传入一个 dom 对象。callback 函数用于在 container 被更新的时候执行。

render 函数除去 if (__DEV__) 语句块和错误校验语句块!isValidContainerLegacy(container)),有效代码只剩下一个 return legacyRenderSubtreeIntoContainer(null, element, container, false, callback)

legacyRenderSubtreeIntoContainer

从函数名来看,这应该是用于实现将虚拟 DOM 树渲染到容器(真实 DOM) 上的功能。它的函数签名如下:

// 将虚拟dom渲染到真实 dom
function legacyRenderSubtreeIntoContainer (
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  // ...
}

它做了以下几件事:

  1. root = container._reactRootContainer,若不存在则创建,用 legacyCreateRootFromDOMContainer 函数创建
  2. 重写 callback 函数,令其被执行时有getPublicRootInstance(root)作为实参传入 callback
  3. 执行 updateContainer(children, fiberRoot, parentComponent, callback), 如果是虚拟dom树是第一次挂载到 container 下,则不直接调用 updateContainer,而是执行 flushSync,在flushSync的回调中执行 updateContainer

它的返回值是一个 getPublicRootInstance(root),其实现如下:

export function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}

返回的是一个 container.current.child.stateNode

legacyCreateRootFromDOMContainer

创建 root 的过程到底做了什么?

function legacyCreateRootFromDOMContainer (
  container: Container,
  forceHydrate: boolean,
): FiberRoot {
// ...
}

函数作用如下:

  1. 如果 forceHydrate 为 false,则清除 container 已存在的子元素
  2. createFiberRoot 函数构造 root。
  3. 执行 markContainerAsRoot(root.current, container)
  4. 对 container 监听所有支持的事件,调了listenToAllSupportedEvents方法

createFiberRoot中,主要做了以下事情:

  1. root = new FiberRootNode(...)
  2. root.current = createHostRootFiber(...)
  3. root.current.stateNode = root(形成了一个循环结构)
  4. 初始化 root.current.memoizedState
  5. 初始化更新队列:initializeUpdateQueue(root.current),事实逻辑如下:
root.current.updateQueue = {
  baseState: fiber.memoizedState,
  firstBaseUpdate: null,
  lastBaseUpdate: null,
  shared: {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
  },
  effects: null,
}