react render阶段
# 01. 整体流程概述
架构体系体现如图所示:
workInProgress 代表当前已创建的 workInProgress fiber。
performUnitOfWork 方法会创建下一个 Fiber 节点并赋值给 workInProgress,并将 workInProgress 与已创建的 Fiber 节点连接起来构成 Fiber 树。
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
setCurrentFiber(unitOfWork);
var next;
if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner$2.current = null;
}
我们知道 Fiber Reconciler 是从 Stack Reconciler 重构而来,通过遍历的方式实现可中断的递归,所以 performUnitOfWork 的工作可以分为两部分:“递”和“归”。
举例说明整个调用顺序
function App() {
return (
<div>
<span>wsh</span>
age: 18
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
render 阶段执行顺序如下:
rootFiber beginWork
App Fiber beginWork
div Fiber beginWork
"age: 18" Fiber beginWork beginWork
"age: 18" Fiber completeWork
span Fiber completeWork
div Fiber completeWork
App Fiber completeWork
rootFiber Fiber completeWork
# 02. 递阶段
首先从 RootFiber 开始向下深度优先遍历。为遍历到的每个 Fiber 节点调用 beginWork 方法。 该方法会根据传入的 Fiber 节点创建子 Fiber 节点,并将这两个 Fiber 节点连接起来。 当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
源码大致如下:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
){
// ...
}
- current:当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,即 workInProgress.alternate
- workInProgress:当前组件对应的 Fiber 节点
- renderLanes:优先级相关,Scheduler 使用
在 mount 首次渲染时,不存在当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,current === null 组件 update 时,current !== null。
通过【current === null】来区分组件是处于 mount 还是 update。
beginWork 的工作可以划分为两部分:
mount 时:除 fiberRootNode 以外,current === null。会根据 fiber.tag 不同,创建不同类型的子 Fiber 节点
update 时:如果 current 存在,在满足一定条件时可以复用 current 节点,这样就能 clone current.child 作为 workInProgress.child,而不需要新建 workInProgress.child。
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
// update时:如果current存在,可能存在优化路径,复用current(即上一次更新的Fiber节点)
if (current !== null) {
// ...省略
// 复用current
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else {
didReceiveUpdate = false;
}
// mount时:根据tag不同,创建不同的子Fiber节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
return mountIndeterminateComponent
case LazyComponent:
// ...省略
return mountLazyComponent
case FunctionComponent:
// ...省略
return updateFunctionComponent
case ClassComponent:
// ...省略
return updateClassComponent()
case HostRoot:
// ...省略
return updateHostRoot()
case HostComponent:
// ...省略
case HostText:
// ...省略
return updateHostText$1()
}
}
# update 时:
- oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
- !includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够
- didReceiveUpdate = false 可以直接复用前一次更新的子Fiber,不需要新建子Fiber
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
switch (workInProgress.tag) {
// ...
}
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else {
didReceiveUpdate = false;
}
} else {
didReceiveUpdate = false;
}
# mount时:
当不满足路径优化时,我们会创建子fiber节点 根据fiber.tag不同,进入不同类型Fiber的创建逻辑,tag (opens new window)
// mount时:根据tag不同,创建不同的Fiber节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent:
// ...省略
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
// ...省略其他类型
}
对于我们常见的组件类型,如(FunctionComponent/ClassComponent/HostComponent),最终会进入reconcileChildren方法。
# reconcileChildren
对于mount的组件,创建新的子Fiber节点
对于update的组件,会将当前组件与该组件在上次更新时对应的Fiber节点比较(Diff算法),将比较的结果生成新Fiber节点
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes
) {
if (current === null) {
// 对于mount的组件,不会为生成的Fiber节点带上effectTag属性
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 对于update的组件,为生成的Fiber节点带上effectTag属性
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
通过代码可以看到 无论是mount或者update ,最终都是将新的子fiber节点赋值给workInProgress.child,作为本次beginWork的返回值,并作为下次performUnitOfWork执行时workInProgress的传参。 其流程图下:
# 03. 归阶段
在“归”阶段会调用 completeWork 处理 Fiber 节点。 当某个 Fiber 节点执行完 completeWork,如果其存在兄弟 Fiber 节点(即 fiber.sibling !== null),会进入其兄弟 Fiber 的“递”阶段。 如果不存在兄弟 Fiber,会进入父级 Fiber 的“归”阶段。 “递”和“归”阶段会交错执行直到“归”到 rootFiber。至此,render 阶段的工作就结束了。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...省略
return null;
}
case HostRoot: {
// ...省略
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
// ...省略
return null;
}
// ...省略
以HostComponent 为例:
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
// workInProgress.stateNode != null 该Fiber节点是否存在对应的DOM节点
if (current !== null && workInProgress.stateNode != null) {
// update的情况
} else {
// mount的情况
}
return null;
}
# update
update,fiber节点山已经存在对应的DOM节点,所以不需要生成DOM节点,需要做的就是关注props的变化,比如:onClick等回调函数的注册,处理style props。 主要逻辑如下:
if (current !== null && workInProgress.stateNode != null) {
// update的情况
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
updateHostComponent内部处理逻辑,被处理完的props会被赋值给workInProgress.updateQueue,并最终在commit阶段被渲染在页面。
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// updatePayload 数据结构 为:[key1, value1, key2, value2]
workInProgress.updateQueue = (updatePayload: any);
# mount
- 为Fiber节点生成对应的DOM节点
- 将子孙DOM节点插入刚生成的DOM节点中
- 与update逻辑中的updateHostComponent类似的处理props的过程
代码简化逻辑如下:
// mount的情况
const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;
// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
# effectList
作为DOM操作的依据,commit阶段需要找到所有有effectTag的Fiber节点并依次执行effectTag对应操作。这个时候我们不需要再次深度遍历。因为 在completeWork的上层函数completeUnitOfWork (opens new window)中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。
effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
在“归”阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表
nextEffect nextEffect
rootFiber.firstEffect -----------> fiber -----------> fiber
hostComponet 归流程: