从源码角度理解 React Render

Render阶段主要创建 Fiber节点,生成Fiber树,在更新阶段进行操作,diff产生具有副作用的effectList

主要有以下重要的步骤:

  • beginWork阶段
  • completeWork阶段

在详细阐述render阶段之前,先看下整个render阶段的流程图;

render阶段主要分为beginWorkcompleteWork两个阶段,也可以称之为递阶段归阶段

我们知道,整个react应用通过JSX解析成了一颗visual DOM树,而要去遍历树结构,去处理每一个节点,则需要使用深度优先遍历(DFS),在react源码中,也是使用的这种方法,不过改用了循环的方式,因为这样可以使执行中断

render 阶段开始于ReactDOM.render(),在这个阶段创建了FiberRoot节点和rootFiber节点,也就是当前应用的根节点,如果存在多应用的话,对应的FiberRoot节点也会存在多个,但是rootFiber每个应用只有一个。

// 创建FiberRoot节点
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建FiberRoot 阶段
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// 创建RootFiber tag = 2
const uninitializedFiber = createHostRootFiber(tag);
// 初始化 current = rootFiber
root.current = uninitializedFiber;
// rootFiber.steteNode = fiberRoot
uninitializedFiber.stateNode = root;
// 初始化更新队列
initializeUpdateQueue(uninitializedFiber);
return root;
}

React.render()方法是在react-dom包中,但是真正协调过程(包括创建``fiberRoot节点以及 rootFiber )是在react-reconciler包中,因此协调算法也可以脱离react`而执行。

接下来进入Render阶段的核心,开始于performSyncWorkOnRoot或者performConcurrentWorkOnRoot同步渲染或者异步渲染(可中断异步渲染)

如果要开启异步渲染,则需要更改ReactDOM的渲染方法

ReactDOM.render(<App />, rootEl); // 同步渲染
ReactDOM.unstable_createRoot(rootEl).render(<App />); // 异步渲染 目前版本处于试验阶段 待18v正式发布

如下,如果使用异步渲染,则需要执行shouldYield调度方法,每处理完一个Fiber节点都需要浏览器当前帧是都还有剩余的空余时间,如果没有,则中断执行,将控制权交还给浏览器。直到下一帧有空余时间。

// 在Fiber上同步执行
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// 在Fiber上并发执行
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}

这里的workInProgress为当前已经都构建的workInProgress fiber节点。

performUnitOfWork主要创建next Fiber节点,完成之后赋值给workInProgress,将workInProgress节点和已创建的Fiber节点通过child或者sibling连接起来,构成整个Fiber树。

beginWork 阶段

从上图可以看出,beginWork阶段主要有两个流程:

  • 首次渲染
  • 更新渲染

首次渲染 - begin

首次渲染主要是首次构建 Fiber 节点以及 Fiber树,构建结束fiberRoot.current会指向当前构建完成的Fiber树,也就是当前页面上展示的DOM结构。

更新渲染 - begin

当发生页面局部更新后,就会重新构建workInProgress Fiber树,此时workInProgress Fiber 树会复用rootFiber节点,然后遍历其子Fiber节点,这里会通过workInProgress.alternate 获取其oldFiber节点,然后与JSX对象(虚拟对象)进行diff算法,如果能够复用,则复用其Fiber节点,不能复用,则创建对应类型的新Fiber节点。直到workInProgress Fiber树构建完成。

详细的过程看后面的章节。

completeWork阶段

从上图可以看出,completeWork阶段也有两个流程:

  • 首次渲染
  • 更新渲染

首次渲染 - complete

completeWork中,首次渲染会在之前创建Fiber节点的基础上创建当前类型的真实DOM,然后挂载到fiber.statenode上。最后为会rootFiber节点打上snapshot= 256flag

在处理下一个Fiber节点之前,会判断当前节点有没有兄弟节点,如果有兄弟节点,则重新走beginWork流程,走完之后从新开始的最后一个节点开始走completeWork流程。

更新渲染 - complete

更新之后,beginWork阶段主要进行协调过程(diff),然后创建新的Fiber节点;

completeWork阶段中,会将新节点老节点进行diffProperties(属性对比),然后会生成一个updateQueueupdateQueue中以类似于(键值对)的形式存储新的值,比如['title', 1],第一个是属性名称,第二个是属性值。

除了构建workInProgress Fiber树之外,在completeWork中会为有更新的Fiber节点打上更新flag,然后根据flag生成effectList(副作用链表),这个链表的起始会传递到rootFiber.firstEffect,如下图:

蓝色背景的节点有更新的节点,右侧生成的就是这次render阶段完成的effectList

详细的过程看后面的章节。