0%

React探索之FiberRootNode

本文是 React探索 系列的第二篇文章。

上一篇文章 总整体上介绍了 React 组件构建及处理流程,对部分细节并没有进行深入的分析。本文重点分析 React 的 render 阶段比较重要的几个概念,便于后续理解 React 的源码。

问题

我们还是从入口函数 render 看起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function render(element, container, callback) {
// ...
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
// ...
var root = container._reactRootContainer;
var fiberRoot;
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
fiberRoot = root._internalRoot;
// ...
// Initial mount should not be batched.
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}

legacyRenderSubtreeIntoContainer 中出现了如下几个变量,我在第一次看源码时有些摸不到头脑,产生了深深的疑问。

  • rootfiberRoot 的关系是什么?
  • fiberRoot 的作用是什么?
  • container._reactRootContainer 的作用是什么?

接下来我们就继续深入源码,逐个解决上面的问题。

RootFiberNode

首先我们来看 root 变量。

1
root = container._reactRootContainer =  legacyCreateRootFromDOMContainer(container, forceHydrate);

这行代码做了两件事:

  1. 调用 legacyCreateRootFromDOMContainer 方法,该方法最终返回一个 FiberRootNode 对象。
  2. rootcontainer._reactRootContainer 复制为同一个对象。

legacyCreateRootFromDOMContainer

继续看 legacyCreateRootFromDOMContainer 方法的源码。

1
2
3
4
5
6
7
function legacyCreateRootFromDOMContainer(container, forceHydrate) {  
var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content.
// ..
return createLegacyRoot(container, shouldHydrate ? {
hydrate: true
} : undefined);
}

先不用管 hydrate 相关的变量(涉及到服务端渲染),我们继续看 createLegacyRoot 方法。

1
2
3
4
5
6
7
function createLegacyRoot(container, options) {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}

createLegacyRoot 内部调用了 createRootImpl 方法,并将 createRootImpl 的返回值赋值给了 ReactDOMBlockingRoot 对象的 _internalRoot 变量。

注意,这个 ReactDOMBlockingRoot 对象就是最终的返回值,也就是最开始的 rootcontainer._reactRootContainer

createRootImpl

继续看 createRootImpl 到底创建了什么。

1
2
3
4
5
6
function createRootImpl(container, tag, options) {
// ...
var root = createContainer(container, tag, hydrate);
// ...
return root;
}

继续看 createContainer

1
2
3
4
5
6
7
8
9
10
11
12
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate);
}
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
var root = new FiberRootNode(containerInfo, tag, hydrate);
// stateNode is any.
var uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}

createContainer 最终通过调用 createFiberRoot 方法创建了一个 FiberRootNode 对象,对应上面代码中的 root 变量。

此外,root.current 指向了一个 createHostRootFiber 创建的对象,该对象是一个 FiberNode 对象。

1
2
3
4
5
6
7
8
9
function createHostRootFiber(tag) {
// ...
return createFiber(HostRoot, null, null, mode);
}

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

是不是已经有点乱了? FiberRootNodeFiberNode 名字怎么这么像,是不是有啥继承关系?

实际并没有关系,大家去看这两个类的定义,会发现是两个完全不同的数据结构。

其中 FiberNode 上一篇文章 已经介绍了,每个 React Element 都会生成一个对应的 FiberNode 对象,最终我们可以得到的是一个 FiberNode 树。

FiberRootNode

FiberRootNode 又是什么呢?我们先看一下其具体定义。

1
2
3
4
5
6
7
8
9
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
// 指向 DOMElement
this.containerInfo = containerInfo;
this.pendingChildren = null;
// 指向 FiberNode
this.current = null;
// ...
}

再结合 createContainer 方法。

1
2
3
4
5
6
7
8
9
10
11
12
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate);
}
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
var root = new FiberRootNode(containerInfo, tag, hydrate);
// stateNode is any.
var uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}

其中,创建 FiberRootNode 对象(root*)时,传入的 *containerInfo 指向 root DOMElement;在 createFiberRoot 中,该 FiberRootNode 对象的 current 属性又被赋值为刚创建的 FiberNode 对象(uninitializedFiber),而这个 FiberNode 对象其实就是将要构建的整个 FiberNode Tree 的根节点(当前为首次构建流程)。

因此 FiberRootNode 对象的 containerInfo 指向了 root DOMElement,current 指向了 root FiberNode,我们大致可以猜出 FiberRootNode 表示的是一个全局根节点。

FiberRootNodeFiberNode ,不知道 React 为啥起了这样两个容易混淆的名字😂。

关系梳理

再回到开头的代码:

1
2
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
fiberRoot = root._internalRoot;
  • legacyCreateRootFromDOMContainer 返回一个 ReactDOMBlockingRoot 对象(root
  • root_internalRoot 属性指向 fiberRoot
  • DOMElement (container*)的 *_reactRootContainer 对象指向 root
  • fiberRootcurrent 属性指向 *FiberNode Tree 的根节点 *rootFiberNode
  • fiberRootcontainerInfo 属性指向 container

fiberRootFiberRootNode 的实例,rootFiberNodeFiberNode 的实例,表示 FiberNode Tree 的根节点。

为了描述得更清晰,我画了个关系图。

关系图

rootFiberNode 也有 stateNode 属性指向 fiberRoot,图中未标出。)

其中的 rootFiberNode 对象,其实就是 上一篇文章 中 Element Tree 的 Root 节点对应的 FiberNode,即 FiberNode Tree 的根节点。

https://blog.jerrychu.top/images/react0/tree1.png

大家可能会有疑问,已经有了 rootFiberNode 了,为什么还需要一个 fiberRoot 呢?

因为 fiberRootNode 是整个应用的根节点,而 rootFiberNode 是组件树的根节点。

ReactDOM.render 是可以被多次调用的,每次调用会渲染不同的组件树,对应不同的rootFiberNode。但是整个应用的根节点只有一个,即 fiberRoot

实际上 fiberRootcurrent 属性指向的节点并不是固定不变的,current 始终指向当前 FiberNode Tree 的根节点。这就涉及到 FiberNode Tree 的更新流程,后面的文章会继续做深入的介绍。

总结

现在我们可以回答文章开始提到的三个问题了。

  • rootfiberRoot 的关系是什么?

直接看这个关系图。

关系图

  • fiberRoot 的作用是什么?

    fiberRoot 是整个应用的根节点, rootFiberNode是组件树的根节点。

  • container._reactRootContainer 的作用是什么?

通过 container 可以直接访问到当前的 fiberRootrootFiberNode

变量打印