背景
前两天看了看jsx转换为reactElement,reactElement转换为Fiber的过程,那我就很好奇了,Fiber怎么变成的dom元素的呢?所以在看了前辈们的课文以及对源码的理解后,打算自己梳理一遍
原理
用react初始化为例
在react初始化的时候,会创建三个全局对象,在三个对象创建完毕的时候,react初始化完毕。
-
ReactDOMRoot对象- 属于
react-dom包,该对象暴露有render,unmount方法, 通过调用该实例的ReactDOM.render方法, 可以引导 react 应用的启动.
- 属于
-
fiberRoot对象- 属于
react-reconciler包,在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态, - 其大部分实例变量用来存储
fiber构造循环过程的各种状态,react 应用内部, 可以根据这些实例变量的值, 控制执行逻辑。
- 属于
-
HostRootFiber对象- 属于
react-reconciler包,这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是HostRoot.
- 属于
抽象的树形结构如上,ReactDomRoot将Dom元素和FiberRoot绑定起来,然后FiberRoot可以切换走向current-Fiber树或者WorkInProGrogress树,这里就是Fiber双缓存结构的基本结构,但是我还没有具体的理解清楚其中的双缓存架构逻辑,以后再看看。
ReactDOMRoot
function ReactDOMRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}
- Container
其中,Element 和 Document 表示不同类型的 DOM 容器:
Element:表示一个 HTML 元素,通常用于表示页面中的某个具体 DOM 节点。Document:表示整个文档,通常用于表示整个 HTML 文档的根节点。
下面的操作就是将reactRootContainer属性绑在Element或者Document下
export type Container =
| (Element & {_reactRootContainer?: RootType, ...})
| (Document & {_reactRootContainer?: RootType, ...});
这里看到_reactRootContainer的类型是RootType,但是我发现牛魔的,RootType和ReactDOMRoot在同一个文件ReactDomRoot,而container的定义在另一个文件 ReactDOMHostConfig.js 中,什么套中套!
options暂时按下不表
RootType
export type RootType = {
render(children: ReactNodeList): void, // 渲染方法
unmount(): void, // 取消挂载
_internalRoot: FiberRoot,
...
};
到这里终于出现了与Fiber挂钩的属性,FiberRoot,阶段性总结一下现在的结构是
那么我们接下来分析一下createRootImpl(container, ConcurrentRoot, options) 函数,主要源码如下
function createRootImpl(
container: Container,
tag: RootTag, // 根节点标记,用于区分LegacyRoot 和 ConcurrentRoot 是 React 的两种根节点类型,它们之间的主要区别在于它们处理渲染和更新的方式
options: void | RootOptions, // 其他配置,与本次关系不大,按下不表
) {
// ...其他配置逻辑
// 创建根容器
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
const containerNodeType = container.nodeType;
// 监听配置
if (enableEagerRootListeners) {
// ...
}
// 数据源注册
if (mutableSources) {
// ...
}
return root;
}
好的,这里知道了根容器root被createContainer创建而得到
export const createContainer = enableNewReconciler
? createContainer_new
: createContainer_old;
继续追踪发现该函数有两个版本,学新不学旧 createContainer_new
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
看到这里你就知道终于来了
FiberRootNode是FiberRoot的构造函数,在这里终于完成了一个 Fiber 树的初始化过程,创建一个 FiberRootNode 实例,并将其赋值给 root,而后构建一个未初始化的Fiber节点,让根节点指向该未初始化的节点,至此完成一棵完整的Fiber树
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 渲染配置
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
// 初始化更新队列
initializeUpdateQueue(uninitializedFiber);
return root;
}
markContainerAsRoot 函数的处理逻辑是将传递的 hostRoot(一个 Fiber 对象)标记为 node(一个 Container 对象)的根容器。具体实现是通过给 node 对象添加一个属性,该属性名由 internalContainerInstanceKey 变量定义,并将该属性的值设置为 hostRoot。这使得 node 对象可以通过该属性引用 hostRoot。
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
// $FlowFixMe[prop-missing]
node[internalContainerInstanceKey] = hostRoot;
}
一张图总结就是
结语
原本我以为的学习路径是
但是现在却暂时变成了
明后天再继续看看这三步是怎么转起来的,哈哈哈
参考
作者:抱枕同学
作者:7km老师