知识回顾
上篇文章我们已经了解到,React渲染更新包含两个阶段:render阶段和commit阶段;render阶段主要是通过调用workLoop函数循环构建workInProgress树,而单个节点的构建工作是由performUnitOfWork函数实现的,这个过程是分为向下遍历和向上回溯,向下遍历由beginWork函数实现,向上回溯由completeWork函数实现。
接下来,让我们结合源码去了解一下这两个函数到底做了哪些工作,具体的实现是怎样的。
一、workLoop
workLoop会调用performUnitOfWork函数来执行一个工作单元,performUnitOfWork会调用beginWork来处理当前组件的更新,如果当前组件没有子节点或子节点已经处理完毕,就会调用completeUnitOfWork向上回溯处理组件的副作用、创建DOM树等。
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
let next = beginWork(current, unitOfWork, renderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
二、beginWork
1、职责
beginWork函数是节点更新的入口,不会直接进行更新操作;它的主要职责包含:
- diff操作;
- 调用
reconcileChildren函数执行diff操作;
- 调用
- 计算更新状态:
- 函数组件:通过
dispatchSetState函数计算新的状态; - 类组件:通过
getStateFromUpdate函数计算新的状态;
- 函数组件:通过
- 执行render函数;
- 函数组件:
renderWithHooks函数会传入一个Component参数,Component就代表函数组件本身,调用该函数就是执行函数式组件; - 类组件:调用
instance.render()执行类组件;
- 函数组件:
2、beginWork函数
function beginWork(
current: Fiber | null, // 当前展示的fiber树
workInProgress: Fiber, // 新构建的fiber树
renderLanes: Lanes // 渲染优先级
): Fiber | null {
if (current !== null) { // current存在,表示非首次渲染
const oldProps = current.memoizedProps; // 获取旧的props
const newProps = workInProgress.pendingProps; // 获取新的props
if (oldProps !== newProps) {
// 新旧props不同,表示有更新;
didReceiveUpdate = true;
} else {
... // 省略部分逻辑
// 否则,无更新,退出beginWork,并返回子树
didReceiveUpdate = false;
// attemptEarlyBailoutIfNoScheduledUpdate该函数会返回当前节点的子节点,进入下一轮performUnitOfWork
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes
);
}
} else {
// current为null,表示首次更新
didReceiveUpdate = false;
}
// 根据不同类型执行不同的更新函数
switch(workInProgress.tag) {
case FunctionComponent: {
// 函数组件更新 ...
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
// 类组件更新 ...
return updateClassComponent(
current,
workInProgress,
Component,
resolveProps,
renderLanes
);
}
}
}
函数组件更新时,会调用updateFunctionComponent,该函数会根据调用hook更新状态,并且调用reconcileChildren执行diff操作;
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// renderWithHooks会调用hook函数初始化与更新state
let nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 执行diff操作,这是diff的核心函数
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
类组件更新时,则调用updateClassComponent函数,该函数会判断组件实例是否存在,以及是否复用该组件实例;并且会调用processUpdateQueue函数执行更新队列,计算新的组件状态。
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
const instance = workInProgress.stateNode; // 获取实例对象,只有类组件有实例
let shouldUpdate;
if (instance === null) { // 组件实例不存在
// new ctor构造组件实例
constructClassInstance(workInProgress, Component, nextProps);
// 初始化更新队列,并设置状态
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// 中断后继续,则复用组件实例
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes,
);
} else {
// 组件更新:会调用processUpdateQueue函数执行更新队列,计算状态
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
// 当前组件执行结束,调用finishClassComponent获取子fiber节点,开启下一轮performUnitOfWork函数
// 该函数会调用reconcileChildren执行diff操作
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
return nextUnitOfWork;
}
三、completeWork
beginWork结束后,就会进入completeWork阶段,completeUnitOfWork函数是completeWork阶段的入口,它会自下而上遍历workInProgress节点;
需要注意的是:该阶段主要负责处理fiber节点到DOM节点的映射,生成真实的DOM树,并没有将DOM插入到真实页面上,它只是在操作fiber上的stateNode;真实的插入DOM操作发生在commit阶段;
1、职责
completeWork阶段的职责主要包含以下几方面:
- 创建DOM节点:
- 在Fiber节点被创建时,对应的真实DOM节点也会被创建,并被赋值到Fiber的stateNode属性上;
- 将DOM节点插入到DOM树中:
- 在completeWork阶段,新创建的DOM节点会被插入到其父节点的子节点列表中,从而逐步构建出整个DOM树;
- 为DOM节点设置属性:
- Fiber节点上的属性信息会被应用到对应的真实DOM节点上,包括元素的类名、样式、事件处理器等;
- 收集副作用:
- 在completeWork阶段,Fiber节点上的副作用信息会被收集并传递到父节点上,以便在后续的commit阶段进行处理,包括需要更新的状态、需要插入或删除的子节点等;
2、completeWork函数
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork: Fiber = unitOfWork;
do {
const current = completedWork.alternate; // 获取当前展示在屏幕上的fiber树
const returnFiber = completedWork.return; // 获取父节点
// 非完整代码,只是用于展示completeWork操作
// 对节点执行completeWork操作
let next = completeWork(current, completedWork, renderLanes);
if (next !== null) {
// 任务被挂起的情况
workInProgress = next;
return;
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 有兄弟节点,就操作兄弟节点
workInProgress = siblingFiber;
return;
}
// 否则,操作父节点
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null);
// 到达根节点
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
completeWork函数用于处理属性、绑定事件、生成DOM树:
- DOM节点的创建:
createInstance实现; - 属性处理及事件绑定:
setInitialProperties实现; - props的处理:
diffProperties实现;
针对不同的类型节点会有不同的操作,以下是简化后的源码:
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case FunctionComponent: // 针对函数组件
bubbleProperties(workInProgress);
return null;
case ClassComponent: { // 针对类组件
const Component = workInProgress.type;
bubbleProperties(workInProgress);
return null;
}
case HostComponent: { // 针对原生DOM节点
const type = workInProgress.type;
// current存在,实例存在,表示更新
if (current !== null && workInProgress.stateNode != null) {
// 执行更新操作
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
// 如果ref变化,标记ref变化
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// 创建DOM节点:
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 插入DOM节点
appendAllChildren(instance, workInProgress, false, false);
// 将DOM节点挂载到实例上
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// 如果原生DOM节点存在ref对象,需要开启一次调度
markRef(workInProgress);
}
bubbleProperties(workInProgress);
return null;
}
}
}
总结
在React中,render阶段是组件更新流程中的一个重要阶段,负责处理组件的渲染逻辑、构建Fiber树、执行diff操作、创建DOM树,在该过程中,每个Fiber节点都会经历从beginWork到completeWork的过程,至此,workInProgress树的构建到此结束,接下来,就会进入到React的commit阶段...
beginWork和completeWork过程中会包含很多细节的实现,后续会慢慢补充细节实现!