实现Reconciler架构
reconciler
是React
核心逻辑所在的模块,中文名叫做协调器
。
reconciler有什么作用?
jQuery
工作原理(过程驱动)
前端框架结构与工作原理(状态驱动)
从图中可以看出,reconciler
接收babel
编译的jsx
,调用jsx
编译形成的ReactElement
数据结构,经过reconciler
等核心模块处理后,调用宿主环境API,如doucument.createElement
、appendChild
等,显示在页面上。
React特点
- 没有消费jsx
- 没有编译优化
- 开放通用API供不同宿主环境使用
在React中的节点类型
- jsx
- ReactElement
- FiberNode
- DOMElement
ReactElement
ReactELement
如果作为核心模块操作的数据结构,存在的问题:
- 无法表达节点之间的关系
- 字段有限,不好扩展(无法表示状态)
所以需要一种新的数据结构,他的特点:
- 介于
ReactElment
与真实UI节点之间 - 能够表达节点之间的关系
- 方便拓展(不仅能作为数据存储单元,也作为工作单元)
这个FiberNode
所具有的特点。
reconciler的工作方式
对于同一个节点,比较其ReactElement
与FiberNode
,生成子FiberNode
。并根据比较的结果生成不同的标记(插入、移动、删除...),对应不同宿主环境API的执行。
比如挂载<div></div>
:
code
// React Element <div></div>
jsx("div")
// 对应fiberNode
null
// 生成子fiberNode
// 对应标记
Placement
将<div></div>
更新为<p></p>
code
// React Element <p></p>
jsx("p")
// 对应fiberNode
FiberNode {type: 'div'}
// 生成子fiberNode
// 对应标记
Deletion Placement
当所有ReactElement
比较完成之后,会生成一棵fiberNode树
,一共会存在两棵fiberNode树
:
- current: 与视图中正式UI对应的
fiberNode树
- workInProgress: 触发更新后
reconciler
中计算的fiberNode
树
jsx消费顺序
以DFS(深度优先遍历)
的顺序遍历ReactElement
,这意味着:
- 如果没有子节点,遍历子节点
- 没有子节点,遍历兄弟节点。
这是个递归过程,存在递、归的两个阶段:
- 递: 对应
beginWork
- 归: 对应
completeWork
DFS深度优先遍历
- 首先任意找一个未被遍历过的顶点,例如从 V1 开始,由于 V1 率先访问过了,所以,需要标记 V1 的状态为访问过;
- 然后遍历 V1 的邻接点,例如访问 V2 ,并做标记,然后访问 V2 的邻接点,例如 V4 (做标记),然后 V8 ,然后 V5 ;
- 当继续遍历 V5 的邻接点时,根据之前做的标记显示,所有邻接点都被访问过了。此时,从 V5 回退到 V8 ,看 V8 是否有未被访问过的邻接点,如果没有,继续回退到 V4 , V2 , V1 ;
- 通过查看 V1 ,找到一个未被访问过的顶点 V3 ,继续遍历,然后访问 V3 邻接点 V6 ,然后 V7 ;
- 由于 V7 没有未被访问的邻接点,所有回退到 V6 ,继续回退至 V3 ,最后到达 V1 ,发现没有未被访问的;
- 最后一步需要判断是否所有顶点都被访问,如果还有没被访问的,以未被访问的顶点为第一个顶点,继续依照上边的方式进行遍历。
根据上述过程可以得出先后顺序如下:
rust
V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7
React触发更新的方式
常见的方式
- ReactDOM.createRoot().render
- this.setState
- useState的
dispatch
方法
实现一套统一的更新机制,主要特点有:
- 兼容上述触发更新的方式
- 方便后续扩展(优先级机制)
更新机制的组成部分
- 代表更新的数据结构(
Update
) - 消费
update
的数据结构(UpdateQueue
)
- 更新可能发生于任意组件,而更新流程是从根节点递归的
- 需要一个统一的根节点保存通用信息
实现
在packages
目录下创建一个react-reconciler
的文件夹,pnpm init
初始化一个package.json
文件,删除package.json
中main
字段,并且往里面添加
json
"module": "index.ts",
"dependencies": {
"shared":"workspace:*"
},
新建个src
目录用来存放源码 下图是用来配置hostConfig
的位置 在src
目录下,创建beginWork.ts
,beginWork.ts
代码如下
ts
import { FiberNode } from './fiber';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const beginWork = (fiber: FiberNode) => {
// 比较,再返回子fiberNode
};
在src
目录下,创建completeWork.ts
,completeWork.ts
代码如下:
ts
import { FiberNode } from './fiber';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const completeWork = (fiber: FiberNode | null) => {
// 递归中的归
};
在src
目录下,创建fiber.ts
,fiber.ts
代码如下:
ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Key, Props, Ref } from 'shared/ReactTypes';
import { WorkTag } from './workTags';
import { Flags, NoFlags } from './fiberFlags';
import { Container } from 'hostConfig';
// 形成FiberNode数据结构
export class FiberNode {
tag: WorkTag;
key: any;
stateNode: any;
pendingProps: Props;
type: any;
return: FiberNode | null;
sibling: FiberNode | null;
child: FiberNode | null;
index: number;
ref: Ref;
memoizedProps: Props | null;
memoizedState: any;
alternate: FiberNode | null;
flags: Flags;
updateQueue: unknown;
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
this.tag = tag;
this.key = key;
this.stateNode = null;
this.type = null;
// 构成树状结构
// 指向父fiberNode
this.return = null;
this.sibling = null;
this.child = null;
this.index = 0;
this.ref = null;
// 作为工作单元
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.memoizedState = null;
this.updateQueue = null;
this.alternate = null;
//副作用
this.flags = NoFlags;
}
}
//FiberRootNode是React中的内部数据结构,用于表示整个React应用的根节点。
export class FiberRootNode {
container: Container;
current: FiberNode;
finishedWork: FiberNode | null;
constructor(container: Container, hostRootFiber: FiberNode) {
this.container = container;
this.current = hostRootFiber;
this.finishedWork = null;
hostRootFiber.stateNode = this;
}
}
//createWorkInProgress是一个内部方法,用于创建一个新的工作中的Fiber节点。
export const createWorkInProgress = (
current: FiberNode,
pendingProps: Props
): FiberNode | null => {
let wip = current.alternate;
if (wip == null) {
//mount
wip = new FiberNode(current.tag, pendingProps, current.key);
wip.stateNode = current.stateNode;
wip.alternate = current;
current.alternate = wip;
} else {
//update
wip.pendingProps = pendingProps;
wip.flags = NoFlags;
}
wip.type = current.type;
wip.updateQueue = current.updateQueue;
wip.child = current.child;
wip.memoizedProps = current.memoizedProps;
wip.memoizedState = current.memoizedState;
return wip;
};
创建fiberFlags.ts
,代码如下,用来标记插入、删除等:
ts
export type Flags = number;
// fiber的标记,位运算
export const NoFlags = 0b0000001; //初始的状态值
export const Placement = 0b0000010; //插入
export const Update = 0b0000100; //组件需要更新时
export const childDeletetion = 0b0001000; //当字节点被删除时
创建fiberReconciler.ts
:
ts
import {
UpdateQueue,
createUpdate,
createUpdateQueue,
enqueueUpdate
} from './updateQueue';
import { Container } from 'hostConfig';
import { FiberNode, FiberRootNode } from './fiber';
import { HostRoot } from './workTags';
import { ReactElementType } from 'shared/ReactTypes';
import { scheduleUpdateOnFiber } from './workLoop';
//createContainer 是 React 的一个重要的内部 API。它的作用是:
// 创建一个 Fiber Root 节点,也就是整个 React 树的根节点
// 将这个 Fiber Root 节点与一个 DOM 容器(如 div 元素)关联起来
// 返回一个容器对象,包含更新容器内 React 树的方法
export function createContainer(container: Container) {
const hostRootFiber = new FiberNode(HostRoot, {}, null);
const root = new FiberRootNode(container, hostRootFiber);
hostRootFiber.updateQueue = createUpdateQueue();
return root;
}
//updateContainer 是 React createContainer 返回的容器对象提供的一个方法,它用于更新已存在的 React 容器内的组件树。
export function updateContainer(
element: ReactElementType | null,
root: FiberRootNode
) {
const hostRootFiber = root.current;
const update = createUpdate<ReactElementType | null>(element);
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
scheduleUpdateOnFiber(hostRootFiber);
return element;
}
创建hostConfig.ts
ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Container = any;
创建updateQueue.ts
ts
import { Action } from 'shared/ReactTypes';
export interface Update<State> {
action: Action<State>;
}
export interface UpdateQueue<State> {
shared: {
pending: Update<State> | null;
};
}
//createUpdate用于创建一个更新对象
export const createUpdate = <State>(action: Action<State>): Update<State> => {
return {
action
};
};
//创建更新队列
//1. 创建一个空的对象来表示这个更新队列
//2. 设置默认属性,如是否需要同步flush同步等
//3. 返回这个更新队列对象
export const createUpdateQueue = <State>() => {
return {
shared: {
pending: null
}
} as UpdateQueue<State>;
};
export const enqueueUpdate = <State>(
updateQueue: UpdateQueue<State>,
update: Update<State>
) => {
updateQueue.shared.pending = update;
};
// 用于处理组件更新队列中的更新操作
// 具体来说,processUpdateQueue 会做以下事情:
// 获取组件的更新队列
// 循环取出队列中的每个更新操作
// 根据更新类型,执行对应的操作
// 如状态更新就调用 setState
// 如 props 更新就调用 componentReceiveProps
// 执行完一个更新后,检查是否需要中断以留出时间给浏览器渲染
// 如果需要中断,将剩余更新推入下次循环
// 如果不需要中断,继续取下一个更新执行
// 所有更新执行完后,标记队列为已处理
// 通过这种方式,processUpdateQueue 可以保证组件树中所有需要更新的组件都能得到有序执行,同时考虑浏览器性能需要中断渲染的情况。
export const processUpdateQueue = <State>(
baseState: State,
pendingUpdate: Update<State> | null
): { memoizedState: State } => {
const result: ReturnType<typeof processUpdateQueue<State>> = {
memoizedState: baseState
};
if (pendingUpdate != null) {
const action = pendingUpdate.action;
if (action instanceof Function) {
// 通过函数更新的方式
result.memoizedState = action(baseState);
} else {
//直接更新
result.memoizedState = action;
}
}
return result;
};
创建workLoop.ts
ts
import { beginWork } from './beginWork';
import { completeWork } from './completeWork';
import { FiberNode, FiberRootNode, createWorkInProgress } from './fiber';
import { HostRoot } from './workTags';
let workInProgress: FiberNode | null = null;
// prepareFreshStack 方法就是用于解决这个问题的。它会做以下工作:
// 保存当前调用栈
// 清空当前调用栈
// 返回一个函数
// 然后在执行真正的组件更新任务时:
// 先调用 prepareFreshStack 返回的函数,清空调用栈
// 执行更新任务
// 如果任务发生错误,错误对象的调用栈将是干净的
function prepareFreshStack(root: FiberRootNode) {
workInProgress = createWorkInProgress(root.current, {});
}
//实现调度功能
// scheduleUpdateOnFiber 的主要作用就是完成这项任务:
// 接收一个需要更新的 Fiber 对象
// 检查该 Fiber 是否已经在更新过程中
// 如果是,直接返回
// 否则继续下一步
// 将更新任务添加到该 Fiber 的更新队列中
// 如果该 Fiber 是 Concurrent 模式,还需要额外的处理
// 如设置需要挂起等标记
// 返回 Fiber 对象
// 之后,在 React 的调度循环中,会不断从各个 Fiber 的更新队列中取出任务执行更新工作。
export function scheduleUpdateOnFiber(fiber: FiberNode) {
let node = fiber;
let parent = node.return;
while (parent != null) {
node = parent;
parent = node.return;
}
if (node.tag === HostRoot) {
return node.stateNode;
}
return null;
}
//从当前的网上遍历到根节点
// markUpdateFromFiberToRoot 是 React 内部的一个方法,它用于在 Fiber 树中标记需要从一个 Fiber 向上冒泡更新到根节点。
// 在 React 中,组件更新可能是从子组件触发的,也可能是从父组件触发的。无论如何,更新都需要从触发点向上冒泡,通知父组件进行更新。
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function markUpdateFromFiberToRoot(fiber: FiberNode) {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function renderRoot(root: FiberRootNode) {
//初始化
prepareFreshStack(root);
do {
try {
workLoop();
break;
} catch (e) {
console.warn('workLoop发生错误', e);
workInProgress = null;
}
} while (true);
}
// workLoop 可以说是 React 更新机制的核心驱动器,它通过不断循环,保证 Fiber 树能按需进行初始化、更新和清理工作。
function workLoop() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
//performUnitOfWork 是 React 中 workLoop 的一个重要子方法,它负责执行单个工作单元(unit of work)。
function performUnitOfWork(fiber: FiberNode) {
const next = beginWork(fiber);
fiber.memoizedProps = fiber.pendingProps;
if (next === null) {
completeUnitOfWork(fiber);
} else {
workInProgress = next;
}
}
// 与performUnitOfWork 配合,完整地执行了单个 Fiber 节点的初始化/更新流程。
// 所以它也是 workLoop 重要的子方法,负责完成单元工作后的后续处理
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);
}
创建workTags.ts
ts
// FunctionComponent:函数组件类型,值是0
// HostRoot:根Fiber节点类型,值是3
// HostComponent:原生DOM组件类型,值是5
// HostText:文本节点类型,值是6
export const FunctionComponent = 0;
export const HostRoot = 3;
export const HostComponent = 5;
export const HostText = 6;
export type WorkTag =
| typeof FunctionComponent
| typeof HostRoot
| typeof HostComponent
| typeof HostText;