序
明确reconciler的作用
在前端框架出现之前,我们熟知的jQuery是过程驱动的,也就是开发者去调用宿主环境的API,然后更新到真实的UI
在前端框架出现之后,我们熟知的react等框架是状态驱动的,也就是开发者只需要描述UI,然后框架的运行时核心模块去调用宿主环境的API,再更新到真实的UI
react的运行时核心模块是reconciler,而vue的运行时核心模块是rendererreact的UI描述方法是JSX,而vue的UI描述方法是模板语法
对于react来说,消费的是jsx,并且没有编译优化,而其他框架有编译优化,也就是说react是一个纯运行时的前端框架,我们的核心模块可以开放通用的API供不同的宿主环境使用,这也就是跨端的方式,比如React Native,React AR,React
明确reconciler消费JSX的过程
在前面我们实现了jsx方法来转换jsx代码,最后返回一个ReactElement数据结构,但是ReactElement无法作为reconciler操作的数据结构,主要是存在以下问题:
ReactElement是没有办法表达节点之间的关系的,只能表达它内部有什么数据ReactElement字段有限,不好拓展,数据只能用于表达当前数据状态,并不能表达当前节点,接下来会发生什么变化
所以我们需要实现一种新的数据结构,他的特点是:
- 介于
ReactElement与真实UI节点之间 - 能够表达各节点之间的关系
- 方便拓展(不仅能作为数据存储单元,也能作为工作单元)
这就是FiberNode(虚拟DOM在React中的实现)
VNode即虚拟DOM在Vue中的实现
Fiber
我们需要明确Fiber的三层含义
- 作为架构来说,React16之前的
Reconciler采用的是递归方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler,React16之后的Reconciler基于Fiber节点实现,被称为Fiber Reconciler - 作为静态的数据结构,每个
Fiber节点对应一个组件,保存了该组件的类型、对应的DOM节点等信息 - 作为动态的工作单元,每个
Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)
首先来实现FiberNode的数据结构
- 新建
packages/react-reconciler/src/fiber.ts
export class FiberNode{
type:any;
tag:WorkTag;
pendingProps:Props;
key:Key;
stateNode:any;
ref:Ref
return:FiberNode|null
sibling:FiberNode|null;
child:FiberNode|null;
index:number
meoizedProps:Props|null;
alternate:FiberNode|null;
flags:Flags;
constructor(tag:WorkTag,pendingProps:Props,key:Key){
// 实例
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.ref=null
// 作为工作单元
this.pendingProps=pendingProps
this.memoizedProps=null
this.alternate=null
// 副作用
this.flags=Noflags
}
}
- 作为一个实例:属性有
tag即Fiber对应组件的类型Function/Class/Host、key、stateNode、type,比如一个HostComponent是div,则stateNode保存的就是div DOM,即对应的真实DOM节点 - 作为表现各节点之间的关系:属性有
return指向父节点,sibling指向兄弟节点,child指向子节点,index例如<ul>li*3</ul>下面有三个li同级,则第一个li为0,第二个li为1 - 作为工作单元:
pendingProps是这个节点刚开始工作的props,memoizedProps是工作确定完了之后的props
我们知道ReactElement是一个存储数据的单元,而FiberNode也可以存储数据,所以我们可以对其中的数据进行比较,根据比较的结果,就能产生很多标记,如果FibrtNode为null的话,比较的结果就会生成子FiberNode,同时对应Placement的标记插入。
我们目前还需要FiberNode类型标识
新建packages/react-reconciler/src/workTags.ts
// FiberNode节点类型标识
export type WorkTag =
| typeof FunctionComponent
| typeof HostRoot
| typeof HostComponent
| typeof HostText;
export const FunctionComponent = 0;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
FiberNode动作标记
新建packages/react-reconciler/src/fiberFlags.ts
export type Flags = number;
export const NoFlags = 0b00000000000000000000000000;
export const Placement = 0b00000000000000000000000010;
export const Update = 0b00000000000000000000000100;
export const ChildDeletion = 0b00000000000000000000010000;
render流程
render阶段,会有两个阶段分别是递阶段和归阶段
递阶段方法为beginWork:与ReactElement进行比较并返回FiberNode
// 递归中的递阶段
export const beginWork = () => {
// 比较,返回子fiberNode
};
归阶段方法为completeWork:
// 归阶段
export const completeWork = () => {
// 递归中的归
};
实现大体的工作流程
新建packages/react-reconciler/src/workLoop.ts
首先需要一个全局的指针指向正在工作的单元workInProgress
// 需要全局的指针指向当前正在工作的单元
let workInprogress: FiberNode | null = null;
实现一个renderRoot函数,调用prepareRefreshStack函数初始化,将指针指向第一个FiberNode,之后执行递归流程,
function prepareFreshStack(fiber: FiberNode) {}
function renderRoot(root: FiberNode) {
// 初始化 让workInprogress指向第一个fiberNode
prepareFreshStack(root);
// 执行递归流程
do {
try {
workLoop();
break;
} catch (e) {
console.warn('workLoop发生错误', e);
workInprogress = null;
}
} while (true);
}
只要指针不为null我们就继续执行performUnitOfWork
function workLoop() {
while (workInprogress !== null) {
performUnitOfWork(workInprogress);
}
}
如果有子节点就遍历子节点,如果没有就调用归阶段流程
function performUnitOfWork(fiber: FiberNode) {
// 可能是fiber的子fiber也可能是null
const next = beginWork(fiber);
fiber.memoizedProps = fiber.pendingProps;
// 如果是子fiber继续向下遍历
if (next === null) {
completeUnitOfWork(fiber);
} 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;
workInprogress = node;
} while (node !== null);
}