1. Reconciler的概念
reconciler是React核心逻辑所在的模块,中文名叫协调器(对标Vue的Render)。协调(reconcile)就是diff算法的意思。React是一个纯运行时的框架,没有模版语法,没有编译优化。
通过jsx的转换,我们能够得到的数据结构是ReactElement,但是它并不能作为reconciler核心模块操作的数据结构,存在的问题如下:
- 无法表达节点之间的关系
- 字段有限,不好拓展
export interface ReactElementType {
$$typeof: symbol | number;
type: ElementType;
key: Key;
props: Props;
ref: Ref;
__mark: string;
}
因此,需要一种新的数据结构FeberNode(对标Vue中的VNode),具备以下特点:
- 介于
ReactElement与真实UI节点之间 - 能够表达节点之间的关系
- 方便拓展(不仅可以记录节点本身的属性,也能够表达节点接下来会发生的变化)
2. 实现FiberNode
在packages目录下新建react-reconciler文件夹,进入文件夹下执行pnpm init,修package.json的内容如下
{
"name": "react-conciler",
"version": "1.0.0",
"description": "React协调器",
"module": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
由于下述fiber.ts当中会引用shared包下的内容,需要在react-reconciler的package.json中添加shared的依赖,添加完成后执行pnpm i,此时react-reconciler包下会出现node_modules/shared
{
"name": "react-conciler",
"version": "1.0.0",
"description": "React协调器",
"module": "index.ts",
"dependencies": {
"shared": "workspace: *"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
在react-reconciler下新建src/fiber.ts,其中会存放fiberNode的数据结构
import { Props, Key, Ref } from 'shared/ReactTypes';
import { WorkTag } from './workTags';
import { Flags, NoFlags } from './fiberFlags';
export class FiberNode {
ref: Ref;
tag: WorkTag; // FiberNode的tag
key: Key; //
stateNode: any; // HostComponent实例 <div> div Dom
type: any; // FunctionComponent本身 () => {}
pendingProps: Props; // 工作单元开始工作前的props
memorizedProps: Props; // 工作单元工作完成后确定下来的props
alternate: FiberNode | null; // 用于在current和workInProgress之间进行切换,如果当前fiberNode是current,这个字段就会指向workInProgress的fiberNode,反之亦然
return: FiberNode | null; // 指向父fiberNode
sibling: FiberNode | null; // 指向同级fiberNode
child: FiberNode | null; // 指向子fiberNode
index: number; // 当前FiberNode在同级FiberNode中的序号
flags: Flags; // fiberNode更新是不同执行方式的标记(移动、删除、新增...)
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
this.ref = null;
this.tag = tag;
this.key = key;
this.stateNode = null;
this.type = null;
// 构成树状结构
this.return = null;
this.sibling = null;
this.child = null;
this.index = 0;
// 作为工作单元
this.pendingProps = pendingProps;
this.memorizedProps = null;
this.alternate = null;
// 副作用
this.flags = NoFlags;
}
}
fiber.ts同级目录下创建workTags.ts存放fiberNode的类型
// 标识当前的FiberNode是什么类型的
export type WorkTag =
| typeof FunctionComponent
| typeof HostRoot
| typeof HostComponent
| typeof HostText;
// 函数式组件
export const FunctionComponent = 0;
// 挂载的根节点
export const HostRoot = 3;
// div
export const HostComponent = 5;
// div下面的文本
export const HostText = 6;
fiber.ts同级目录下创建fiberFlags存放fiberNode执行不同操作的标记
3. Reconciler的工作方式
对于同一个节点,比较其ReactElement与fiberNode,生成子fiberNode。并根据比较的结果生成不同的标记(插入、删除、移动...),对应不同宿主环境API的执行。
举例:
- 挂载
<div></div>
React Element --> jsx("div")
对应fiberNode --> null
生成子fiberNode
对应标记 --> Placement
- 将
<div></div>更新为<p></p>
React Element --> jsx("p")
对应fiberNode --> FiberNode {type: 'div'}
生成子fiberNode
对应标记 --> Deletion Placement
当所有ReactElement比较完后,会生成一颗fiberNode树,一共会存在两颗fiberNode树:
- current: 与视图中真实UI对应的
fiberNode树 - workInProgress: 触发更新后,正在
reconciler中计算的fiberNode树
3. 递归遍历
react是以DFS(深度优先遍历)的顺序遍历ReactElement,这意味着:
- 如果有子节点,优先处理子节点
- 如果没有子节点,遍历兄弟节点
这是一个递归的过程,存在递、归两个阶段
- 递:对应
beginWork - 归:对应
completeWork
在react-reconciler下新建beginWork.ts和completeWork.ts两个文件,分别对应递归过程中递和归的处理逻辑,文件内容如下
// beginWork.ts 对应递归中的递阶段
export const beginWork = () => {};
// completeWork.ts 对应递归中的归
export const completeWork = () => {};
在react-reconciler下新建workLoop.ts文件,用于调用beginWork和completeWork,文件内容如下
import { beginWork } from './beginWork';
import { completeWork } from './completeWork';
import { FiberNode } from './fiber';
// 指向全局唯一正在工作的FiberNode
let workInProgress: FiberNode | null = null;
// 用于执行初始化的操作
function prepareFreshStack(fiber: FiberNode) {
workInProgress === fiber;
}
function renderRoot(root: FiberNode) {
// 初始化,让workInProgress指向第一个FiberNode
prepareFreshStack(root);
// 执行递归的过程
do {
try {
workLoop();
break;
} catch (e) {
console.warn('workLoop发生错误', e);
workInProgress = null;
}
} while (true);
}
function workLoop() {
while (workInProgress! == null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(fiber: FiberNode) {
const next = beginWork(fiber);
// fiber执行完,就可以将pendingProps赋值给memorizedProps
fiber.memorizedProps = fiber.pendingProps;
// 代表当前fiber没有子节点了,该横向遍历兄弟节点了
if (next === null) {
completeUnitOfWork(fiber);
}
// 如果next有值,说明没有到最底层,重新赋值workInProgress,继续向下递归
else {
workInProgress = next;
}
}
function completeUnitOfWork(fiber: FiberNode) {
let node: FiberNode | null = fiber;
do {
completeWork(node);
const sibling = node.sibling;
if (sibling !== null) {
workInProgress = sibling;
return;
}
// 如果不存在兄弟节点,应该递归到上一层级
node = node?.return;
} while (node !== null);
}
代码整体的执行逻辑如下
对应的jsx树的执行逻辑如下