先从单个原生标签节点开始实现,后面文章会陆续添加多个节点、文本节点、Fragment、类组件以及函数组件的实现。
js
// 单个原生标签节点
const jsx = (
<div className="box border">
<h1 className="border">omg</h1>
</div>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);
一切从根节点开始。
首先创建根节点,函数为 createRoot
,入参为根 Fiber。
js
export function createRoot(container: Container): RootType {
const root: FiberRoot = createFiberRoot(container); // createFiberRoot 参考文章 https://juejin.cn/post/7524141053446094875
return new ReactDOMRoot(root);
}
createRoot
返回 ReactDOMRoot
的实例,实例上有三个属性 render、unmount 和 _internalRoot。
- render - 渲染
- unmount - 卸载
- _internalRoot - React 内部变量
render 和 unmount 都依赖 updateContainer
函数。
js
function ReactDOMRoot(_internalRoot: FiberRoot) {
this._internalRoot = _internalRoot;
}
ReactDOMRoot.prototype.render = function (children: ReactNodeList) {
updateContainer(children, this._internalRoot);
};
// unmount 省略
updateContainer
调用 scheduleUpdateOnFiber
实现调度更新。
js
export function updateContainer(element: ReactNodeList, container: FiberRoot) {
// 1. 获取 current
const current = container.current;
current.memoizedState = { element };
// 2. 调度更新
scheduleUpdateOnFiber(container, current);
}
scheduleUpdateOnFiber
调用 performConcurrentWorkOnRoot
。performConcurrentWorkOnRoot
包含了两个重要阶段:render 阶段和 commit 阶段。
js
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
isSync?: boolean
) {
workInProgressRoot = root;
workInProgress = fiber;
if (isSync) {
// queueMicrotask 是 JS 原生 API
queueMicrotask(() => performConcurrentWorkOnRoot(root));
}
}
export function performConcurrentWorkOnRoot(root: FiberRoot) {
// 1. render, 构建 fiber 树,即 VDOM(beginWork|completeWork)
renderRootSync(root);
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork; // 根 Fiber
// 2. commit, VDOM -> DOM
commitRoot(root);
}
Render 阶段
Render 阶段创建 fiber 树,入口函数是 renderRootSync
。
performUnitOfWork
函数包含了 render 阶段的两个重要子阶段 beginWork
和 completeWork
。
js
function renderRootSync(root: FiberRoot) {
// 1. render 阶段开始
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
// 2. 初始化
prepareFreshStack(root);
// 3. 遍历构建 fiber 树
workLoopSync();
// 4. render 结束
executionContext = prevExecutionContext;
workInProgressRoot = null;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber) {
const current = unitOfWork.alternate;
// 1. beginWork
let next = beginWork(current, unitOfWork);
// 把 pendingProps 更新到 memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 1.1 执行自己
// 1.2 (协调,bailout)返回子节点
if (next === null) {
// 没有产生新的 work
// 2. completeWork
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
BeginWork 阶段
beginWork
主要做两件事:
- 更新自己
- 协调子节点
实际上,
begingWork
要处理的事情很多:
- 区分单个节点和多个节点
- 区分不同的节点类型,比如类组件、函数组件等
- 区分渲染和更新
js
// 1. 处理当前 fiber,因为不同组件对应的 fiber 处理方式不同,
// 2. 返回子节点 child
export function beginWork(
current: Fiber | null,
workInProgress: Fiber
): Fiber | null {
switch (workInProgress.tag) {
// 根节点
case HostRoot:
return updateHostRoot(current, workInProgress);
// 原生标签,div、span...
case HostComponent:
return updateHostComponent(current, workInProgress);
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
"React. Please file an issue."
);
}
beginWork
根据 tag 调用不同的 update 函数,初次渲染主要涉及两个函数:updateHostRoot
和 updateHostComponent
。
js
// 根节点
function updateHostRoot(current: Fiber | null, workInProgress: Fiber) {
const nextChildren = workInProgress.memoizedState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function shouldSetTextContent(type: string, props: any): boolean {
return (
type === "textarea" ||
type === "noscript" ||
isStr(props.children) ||
isNum(props.children) ||
(typeof props.dangerouslySetInnerHTML === "object" &&
props.dangerouslySetInnerHTML !== null &&
props.dangerouslySetInnerHTML.__html != null)
);
}
// 原生标签,div、span...
// 初次渲染 协调
function updateHostComponent(current: Fiber | null, workInProgress: Fiber) {
const { type, pendingProps } = workInProgress;
const isDirectTextChild = shouldSetTextContent(type, pendingProps);
if (isDirectTextChild) {
// 文本属性没有子节点
return null;
}
// 如果原生标签只有一个文本,这个时候文本不会再生成 fiber 节点,而是当做这个原生标签的属性
const nextChildren = pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
updateHostRoot
和 updateHostComponent
都依赖 reconcileChildren
函数。
js
// 协调子节点,构建新的 fiber 树
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any
) {
if (current === null) {
// 初次挂载
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
// 更新
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren
);
}
}
reconcileChildren
区分初次挂载和更新,对应函数 mountChildFiber
和 reconcileChildFibers
。两者都调用了 createChildReconciler
只是参数不同。
js
export const reconcileChildFibers: ChildReconciler =
createChildReconciler(true);
export const mountChildFibers: ChildReconciler = createChildReconciler(false);
createChildReconciler
是一个 wrapper function(函数里套函数)。
js
// 协调子节点
function createChildReconciler(shouldTrackSideEffects: boolean) {
// 给 fiber 节点添加 flags
function placeSingleChild(newFiber: Fiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement; // 节点可能既修改属性又移动位置,所以用位运算,可叠加
}
return newFiber;
}
// 协调单个节点,对于页面初次渲染,创建 fiber,不涉及对比复用老节点
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement
) {
let createdFiber = createFiberFromElement(element);
createdFiber.return = returnFiber;
return createdFiber;
}
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any
) {
// 检查 newChild 类型,单个节点
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild)
);
}
}
}
return null;
}
return reconcileChildFibers;
}
CompleteWork 阶段
completeUnitOfWork
阶段使用了深度优先遍历,其中重点函数是 completeWork
。
js
// 深度优先遍历,子节点、兄弟节点、叔叔节点、爷爷的兄弟节点...(王朝的故事)
function completeUnitOfWork(unitOfWork: Fiber) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
let next = completeWork(current, completedWork);
if (next !== null) {
workInProgress = next;
return;
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber as Fiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
completeWork
函数实现了根节点和原生标签两个 case。
js
function completeWork(
current: Fiber | null,
workInProgress: Fiber
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostRoot: {
return null;
}
case HostComponent: {
// 原生标签,type 是标签名
const { type } = workInProgress;
// 1. 创建真实 DOM
const instance = document.createElement(type);
// 2. 初始化 DOM 属性
finalizeInitialChildren(instance, newProps);
// 3. 把子 dom 挂载到父 dom 上
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
return null;
}
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
"React. Please file an issue."
);
}
// 初始化、更新属性
function finalizeInitialChildren(
domElement: Element,
props: any
) {
// 遍历 props
for (const propKey in props) {
const value = props[propKey];
if (propKey === "children") {
if (isStr(value) || isNum(value)) {
// 属性
domElement.textContent = value + "";
}
} else {
// 设置属性
(domElement as any)[propKey] = value;
}
}
}
function appendAllChildren(parent: Element, workInProgress: Fiber) {
let nodeFiber = workInProgress.child; // 链表结构
if (nodeFiber) {
parent.appendChild(nodeFiber.stateNode);
}
}
Commit 阶段
Commit 阶段将 VDOM 转换成 DOM,入口函数是 commitRoot
。
js
function commitRoot(root: FiberRoot) {
// 1. commit 阶段开始
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// 2 mutation 阶段, 渲染 DOM 树
commitMutationEffects(root, root.finishedWork as Fiber); //Fiber,HostRoot=3
// 3. commit 结束
executionContext = prevExecutionContext;
workInProgressRoot = null;
}
function commitMutationEffects(root: FiberRoot, finishedWork: Fiber) {
// 1. 遍历
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
}
function recursivelyTraverseMutationEffects(
root: FiberRoot,
parentFiber: Fiber
) {
let child = parentFiber.child;
// 遍历单链表
while (child !== null) {
commitMutationEffects(root, child);
child = child.sibling;
}
}
// 提交协调的产生的 effects,比如 flags,Placement、Update、ChildDeletion
function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
if (flags & Placement) {
// 页面初次渲染 新增插入 appendChild
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement; // 删除 Placement
}
}
function commitPlacement(finishedWork: Fiber) {
if (finishedWork.stateNode && finishedWork.tag === HostComponent) {
// finishedWork 是有 dom 节点
const domNode = finishedWork.stateNode
// 找 domNode 的父 DOM 节点对应的 fiber
const parentFiber = getHostParentFiber(finishedWork);
let parentDom = parentFiber.stateNode;
if (parentDom.containerInfo) {
// HostRoot
parentDom = parentDom.containerInfo;
}
parentDom.appendChild(domNode)
}
}
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
throw new Error(
"Expected to find a host parent. This error is likely caused by a bug " +
"in React. Please file an issue."
);
}
// 检查 fiber 是 HostParent
function isHostParent(fiber: Fiber): boolean {
return fiber.tag === HostComponent || fiber.tag === HostRoot;
}