本文是 React探索 系列的第二篇文章。
上一篇文章 总整体上介绍了 React 组件构建及处理流程,对部分细节并没有进行深入的分析。本文重点分析 React 的 render 阶段比较重要的几个概念,便于后续理解 React 的源码。
问题
我们还是从入口函数 render 看起。
1 | function render(element, container, callback) { |
在 legacyRenderSubtreeIntoContainer 中出现了如下几个变量,我在第一次看源码时有些摸不到头脑,产生了深深的疑问。
- root 和 fiberRoot 的关系是什么?
- fiberRoot 的作用是什么?
- container._reactRootContainer 的作用是什么?
接下来我们就继续深入源码,逐个解决上面的问题。
RootFiberNode
首先我们来看 root 变量。
1 | root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate); |
这行代码做了两件事:
- 调用 legacyCreateRootFromDOMContainer 方法,该方法最终返回一个
FiberRootNode
对象。 - 将 root 与 container._reactRootContainer 复制为同一个对象。
legacyCreateRootFromDOMContainer
继续看 legacyCreateRootFromDOMContainer 方法的源码。
1 | function legacyCreateRootFromDOMContainer(container, forceHydrate) { |
先不用管 hydrate 相关的变量(涉及到服务端渲染),我们继续看 createLegacyRoot 方法。
1 | function createLegacyRoot(container, options) { |
createLegacyRoot 内部调用了 createRootImpl 方法,并将 createRootImpl 的返回值赋值给了 ReactDOMBlockingRoot
对象的 _internalRoot 变量。
注意,这个 ReactDOMBlockingRoot
对象就是最终的返回值,也就是最开始的 root 和 container._reactRootContainer。
createRootImpl
继续看 createRootImpl 到底创建了什么。
1 | function createRootImpl(container, tag, options) { |
继续看 createContainer。
1 | function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) { |
createContainer 最终通过调用 createFiberRoot 方法创建了一个 FiberRootNode
对象,对应上面代码中的 root 变量。
此外,root.current 指向了一个 createHostRootFiber 创建的对象,该对象是一个 FiberNode
对象。
1 | function createHostRootFiber(tag) { |
是不是已经有点乱了? FiberRootNode
和 FiberNode
名字怎么这么像,是不是有啥继承关系?
实际并没有关系,大家去看这两个类的定义,会发现是两个完全不同的数据结构。
其中 FiberNode 上一篇文章 已经介绍了,每个 React Element 都会生成一个对应的 FiberNode 对象,最终我们可以得到的是一个 FiberNode 树。
FiberRootNode
那 FiberRootNode
又是什么呢?我们先看一下其具体定义。
1 | function FiberRootNode(containerInfo, tag, hydrate) { |
再结合 createContainer 方法。
1 | function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) { |
其中,创建 FiberRootNode
对象(root*)时,传入的 *containerInfo 指向 root DOMElement;在 createFiberRoot 中,该 FiberRootNode
对象的 current 属性又被赋值为刚创建的 FiberNode
对象(uninitializedFiber),而这个 FiberNode
对象其实就是将要构建的整个 FiberNode Tree 的根节点(当前为首次构建流程)。
因此 FiberRootNode
对象的 containerInfo 指向了 root DOMElement,current 指向了 root FiberNode,我们大致可以猜出 FiberRootNode
表示的是一个全局根节点。
FiberRootNode
和FiberNode
,不知道 React 为啥起了这样两个容易混淆的名字😂。
关系梳理
再回到开头的代码:
1 | root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate); |
- legacyCreateRootFromDOMContainer 返回一个
ReactDOMBlockingRoot
对象(root) - root 的 _internalRoot 属性指向 fiberRoot。
- DOMElement (container*)的 *_reactRootContainer 对象指向 root。
- fiberRoot 的 current 属性指向 *FiberNode Tree 的根节点 *rootFiberNode。
- fiberRoot 的 containerInfo 属性指向 container。
fiberRoot 是
FiberRootNode
的实例,rootFiberNode 是FiberNode
的实例,表示 FiberNode Tree 的根节点。
为了描述得更清晰,我画了个关系图。
rootFiberNode 也有 stateNode 属性指向 fiberRoot,图中未标出。)
其中的 rootFiberNode 对象,其实就是 上一篇文章 中 Element Tree 的 Root 节点对应的 FiberNode
,即 FiberNode Tree 的根节点。
大家可能会有疑问,已经有了 rootFiberNode 了,为什么还需要一个 fiberRoot 呢?
因为 fiberRootNode 是整个应用的根节点,而 rootFiberNode 是组件树的根节点。
ReactDOM.render 是可以被多次调用的,每次调用会渲染不同的组件树,对应不同的rootFiberNode。但是整个应用的根节点只有一个,即 fiberRoot。
实际上 fiberRoot 的 current 属性指向的节点并不是固定不变的,current 始终指向当前 FiberNode Tree 的根节点。这就涉及到 FiberNode Tree 的更新流程,后面的文章会继续做深入的介绍。
总结
现在我们可以回答文章开始提到的三个问题了。
- root 和 fiberRoot 的关系是什么?
直接看这个关系图。
fiberRoot 的作用是什么?
fiberRoot 是整个应用的根节点, rootFiberNode是组件树的根节点。
container._reactRootContainer 的作用是什么?
通过 container 可以直接访问到当前的 fiberRoot 和 rootFiberNode。