小白图解DOM元素转换为Fiber结构!!!

163 阅读4分钟

背景

前两天看了看jsx转换为reactElement,reactElement转换为Fiber的过程,那我就很好奇了,Fiber怎么变成的dom元素的呢?所以在看了前辈们的课文以及对源码的理解后,打算自己梳理一遍

原理

用react初始化为例

在react初始化的时候,会创建三个全局对象,在三个对象创建完毕的时候,react初始化完毕。

  1. ReactDOMRoot对象

    1. 属于react-dom包,该对象暴露有render,unmount方法, 通过调用该实例的ReactDOM.render方法, 可以引导 react 应用的启动.
  2. fiberRoot对象

    1. 属于react-reconciler包,在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态,
    2. 其大部分实例变量用来存储fiber构造循环过程的各种状态,react 应用内部, 可以根据这些实例变量的值, 控制执行逻辑。
  3. HostRootFiber对象

    1. 属于react-reconciler包,这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是HostRoot.

image.png 抽象的树形结构如上,ReactDomRoot将Dom元素和FiberRoot绑定起来,然后FiberRoot可以切换走向current-Fiber树或者WorkInProGrogress树,这里就是Fiber双缓存结构的基本结构,但是我还没有具体的理解清楚其中的双缓存架构逻辑,以后再看看。

ReactDOMRoot

function ReactDOMRoot(container: Container, options: void | RootOptions) {
  this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}
  1. Container

其中,ElementDocument 表示不同类型的 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,阶段性总结一下现在的结构是

image.png

那么我们接下来分析一下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

markContainerAsRoot 函数的处理逻辑是将传递的 hostRoot(一个 Fiber 对象)标记为 node(一个 Container 对象)的根容器。具体实现是通过给 node 对象添加一个属性,该属性名由 internalContainerInstanceKey 变量定义,并将该属性的值设置为 hostRoot。这使得 node 对象可以通过该属性引用 hostRoot

export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
  // $FlowFixMe[prop-missing]
  node[internalContainerInstanceKey] = hostRoot;
}

一张图总结就是

image.png

结语

原本我以为的学习路径是

image.png

但是现在却暂时变成了

image.png

明后天再继续看看这三步是怎么转起来的,哈哈哈

参考

作者:抱枕同学

链接:juejin.cn/post/720208…

作者:7km老师

链接:7km.top/main/macro-…