React18源码-react-reconciler包实现

实现Reconciler架构

reconcilerReact核心逻辑所在的模块,中文名叫做协调器

reconciler有什么作用?

jQuery工作原理(过程驱动)

前端框架结构与工作原理(状态驱动)

从图中可以看出,reconciler接收babel编译的jsx,调用jsx编译形成的ReactElement数据结构,经过reconciler等核心模块处理后,调用宿主环境API,如doucument.createElementappendChild等,显示在页面上。

React特点

  • 没有消费jsx
  • 没有编译优化
  • 开放通用API供不同宿主环境使用

在React中的节点类型

  • jsx
  • ReactElement
  • FiberNode
  • DOMElement

ReactElement

ReactELement如果作为核心模块操作的数据结构,存在的问题:

  • 无法表达节点之间的关系
  • 字段有限,不好扩展(无法表示状态)

所以需要一种新的数据结构,他的特点:

  • 介于ReactElment与真实UI节点之间
  • 能够表达节点之间的关系
  • 方便拓展(不仅能作为数据存储单元,也作为工作单元)

这个FiberNode所具有的特点。

reconciler的工作方式

对于同一个节点,比较其ReactElementFiberNode,生成子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.jsonmain字段,并且往里面添加

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;
相关推荐
蟾宫曲2 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心2 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455662 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029403 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟4 小时前
css文字折行以及双端对齐实现方式
前端·css
2401_882726485 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203985 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww5 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator
前端没钱5 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
m0_748255266 小时前
vue3导入excel并解析excel数据渲染到表格中,纯前端实现。
前端·excel