本文系列较为基础,适合react新手阅读。阅读过程中如果发现任何错误,麻烦评论区指正.
React版本:React17.0.0。
引言
本文旨在将render过程捋顺,优先级和调度器内容非必要不会涉及。
performSyncWorkOnRoot
首先我们从上一篇文章结尾的performSyncWorkOnRoot函数开始梳理,下边是经过极限简化版的performSyncWorkOnRoot函数:
function performSyncWorkOnRoot(root) {
var exitStatus;
exitStatus = renderRootSync(root, lanes); // render阶段入口
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork; //effectList相关
root.finishedLanes = lanes;
commitRoot(root); // commit 入口
ensureRootIsScheduled(root, now());
return null;
}
在这个函数中,我们可以很清晰的看到render阶段和commit的入口。这篇文章只了解render阶段,也就是 renderRootSync函数。
renderRootSync
function renderRootSync(root, lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
//初始化是workInProgressRoot为空,在prepareFreshStack函数中会为workInProgressRoot进行赋值
//同时为workInProgress进行赋值,此时为RootFiber节点。
prepareFreshStack(root, lanes);
}
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
- 在prepareFreshStack函数中为workInProgress赋值,保证工作循环的开展。
- 在renderRootSync中会调用workLoopSync函数,该函数的作用主要是开启一个同步的工作循环,用于构建和更新React组件的Fiber树。在工作循环中,会循环调用performUnitOfWork创建Fiber节点。
performUnitOfWork
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;// currentFiber
var next;
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);//深入优先遍历Fiber的“递”
unitOfWork.memoizedProps = unitOfWork.pendingProps;//更新Fiber节点上的props
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);// 深入优先遍历的“归”
} else {
workInProgress = next;
}
ReactCurrentOwner$2.current = null;
}
在performUnitOfWork函数中,会调用beginWork$1和completeUnitOfWork,这两个方法中会分别调用beginWork和completeWork,这两个方法就是构建fiber树的"递归"过程。
beginwork(Mount)
function beginWork(current, workInProgress, renderLanes) {
var updateLanes = workInProgress.lanes;
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {//mount阶段
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {//匹配fiber阶段的tag 进入到不同处理方法中
//...
case FunctionComponent://函数组件的处理方法
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
}
case HostRoot:// 根节点的处理方法
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent: //可对应一个DOM的Fiber节点的处理方法
return updateHostComponent(current, workInProgress, renderLanes);
//....
}
}
beginWork的入参解析:
- current:当前已经渲染完成的Fiber节点
- workInProgress:正在构建新的Fiber树的节点
- renderLanes:优先级相关本文不涉及
beginWork函数首先会根据current是否存在来判断是mount阶段还是update阶段。
mount阶段:就会根据workInProgress Fiber节点的tag来判断Fiber节点的类型,然后进入到相应的节点处理方法。
update阶段:则会判断能否进行复用
- oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
- !includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够。
满足以上两个条件:进入到bailoutOnAlreadyFinishedWork方法中进行复用当前的Fiber节点,也就是current。
不满足:流程会走到,updateXXX处理方法,会在方法内部进行区分是mount还是update。
reconcileChildren
下边主要看对应DOM节点的Fiber节点类型如何处理。updateHostComponent函数中主要调用的是reconcileChildren函数:
function updateHostComponent(current, workInProgress, renderLanes) {
//....
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
- RootFiber节点获取到子JSX节点是通过先前创建的update,通过计算RootFiber的state得到的。
- 根组件,也就是App组件的子JSX节点是通过执行函数组件,获取其返回值得到的。
- 对应DOM节点的Fiber节点获取子JSX节点,是在处理函数组件时,已经确立了JSX节点之间的阶段关系,放在了pendingProps中。
可以看到reconcileChildren的流程是:判断current,走对应的流程函数,并将返回值(处理好的Fiber)赋值给workInProgress.child。并且之后持续进行工作循环的条件就是workInProgress不为null。 所以beginWork的作用可以理解为根据已经存在workInProgress Fiber节点和节点的子JSX节点元素去构建下一个workInProgress。
mountChildFibers与reconcileChildFibers这两个方法的逻辑基本一致。唯一的区别是:reconcileChildFibers会为生成的fiber节点带上effectTag属性,而mountChildFibers不会。
这块使用了闭包,利用ChildReconciler函数的作用域保存了shouldTrackSideEffects变量,mountChildFibers就会走shouldTrackSideEffects为false的逻辑,即不为生成的fiber节点架effectTag标记。reconcileChildFibers方法则会添加。
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
var isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
case REACT_LAZY_TYPE:
{
var payload = newChild._payload;
var init = newChild._init; // TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
}
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes));
}
if (isArray$1(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
}
//...
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
进入mountChildFibers方法,实际上是进入了在ChildReconciler函数内部定义的reconcileChildFibers方法中,该方法会根据workInProgress的子JSX节点的类型来判断如何处理,比如,如果newChild的类型是对象并且$$typeof属性是REACT_ELEMENT_TYPE的话,那就是单一子节点走reconcileSingleElement处理函数,如果是数组的话,走reconcileChildrenArray处理函数。
reconcileSingleElement
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key;
var child = currentFirstChild;
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
}
小结:beginWork作用就是根据workInProgress,即returnFiber的JSX子节点,创建Fiber节点,然后将创建好的节点与workInProgress通过指针构成链表组成Fiber树。如果没有后代节点了,子JSX为空,最后就会返回空。那就会执行到performUnitOfWork中next判断为null的逻辑:completeUnitOfWork。
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
setCurrentFiber(unitOfWork);
var next;
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
completeWork(Mount)
在completeUnitOfWork中会调用completeWork,并会根据Fiber节点的类型(tag)进入到不同的处理流程中,mount阶段处理对应DOM节点的Fiber节点的主要逻辑就是:
//通过判断current Fiber节点和判断workInProgress节点上的DOM是否创建
if (current !== null && workInProgress.stateNode != null) {
//...update阶段逻辑
} else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
workInProgress.stateNode = instance; // Certain renderers require commit-time
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
在createInstance函数中,主要就是根据传入的属性调用createElement方法创建DOM节点,并返回出来。将DOM节点赋值给workInProgress Fiber的stateNode属性,这样就把Fiber节点与DOM节点联系起来了。最后一步就是调用finalizeInitialChildren方法初始化刚创建的DOM的属性。
beginwork(Update)
update阶段主要是根据下边两个条件观察能否复用Fiber节点:
- oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
- !includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够。
如果可以复用的话就会走bailoutOnAlreadyFinishedWork函数复用Current Fiber节点。
function beginWork(current, workInProgress, renderLanes) {
var updateLanes = workInProgress.lanes;
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
如果不能完全复用,需要更新的话,流程仍然会走到reconcileChildren函数中,在上文中,我们提到了在reconcileChildren函数中会根据current区分mount和update阶段。update阶段和mount阶段一样,同样会走到reconcileSingleElement函数,在该函数内将当前组件与该组件在上次更新时对应的Fiber节点比较(单节点Diff算法),将比较的结果生成新Fiber节点。beginWork的作用可以总结为以下两点:
- mount阶段:新建Fiber节点,并建立节点之间的联系
- update阶段:对比新老Fiber节点,根据对比结果生成新的节点
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key;
var child = currentFirstChild;
//child节点不为null执行对比(Diff算法)
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
switch (child.tag) {
case Fragment:
{
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
var existing = useFiber(child, element.props.children);
existing.return = returnFiber;
{
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
case Block:
{
var type = element.type;
if (type.$$typeof === REACT_LAZY_TYPE) {
type = resolveLazyType(type);
}
if (type.$$typeof === REACT_BLOCK_TYPE) {
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
if (type._render === child.type._render) {
deleteRemainingChildren(returnFiber, child.sibling);
var _existing2 = useFiber(child, element.props);
_existing2.type = type;
_existing2.return = returnFiber;
{
_existing2._debugSource = element._source;
_existing2._debugOwner = element._owner;
}
return _existing2;
}
}
}
// We intentionally fallthrough here if enableBlocksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default:
{
if (child.elementType === element.type || ( // Keep this check inline so it only runs on the false path:
isCompatibleFamilyForHotReloading(child, element) )) {
deleteRemainingChildren(returnFiber, child.sibling);
var _existing3 = useFiber(child, element.props);
_existing3.ref = coerceRef(returnFiber, child, element);
_existing3.return = returnFiber;
{
_existing3._debugSource = element._source;
_existing3._debugOwner = element._owner;
}
return _existing3;
}
break;
}
} // Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
//...mount阶段创建Fiber节点
}
completeWork(Update)
//通过判断current Fiber节点和判断workInProgress节点上的DOM是否存在
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
} else {
//...上文讲过的mount阶段
}
}
updateHostComponent$1->prepareUpdate-> diffProperties 这是updateHostComponent$1函数的调用栈,最终会进入diffProperties函数。 下边的代码涉及到了一个知识点for in不仅仅会遍历对象自身的属性,还会遍历其原型链上的可枚举属性。所以会通过hasOwnProperty方法进行判断。
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
var updatePayload = null;
var lastProps;
var nextProps;
switch (tag) {
//...
default:
lastProps = lastRawProps;
nextProps = nextRawProps;
break;
}
var propKey;
var styleName;
var styleUpdates = null;
for (propKey in lastProps) {
// 新老props都存在,update情况
// 属性是存在原型链上,不是在对象自身上
// 老props属性为空
// 3种情况跳过,走下一轮循环
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
continue;
}
// 能走到这里的都是新props不存在,老props存在的属性,也就是需要删除的属性
//处理style
if (propKey === STYLE) {
var lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
}
// 处理特殊props
else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) ; else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (!updatePayload) {
updatePayload = [];
}
} else {
// 处理常规props,将清空propsKey的动作加入到updateQueue中
(updatePayload = updatePayload || []).push(propKey, null);
}
}
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
continue;
}
if (propKey === STYLE) {
{
if (nextProp) {
// Freeze the next style object so that we can assume it won't be
// mutated. We have already warned for this in the past.
Object.freeze(nextProp);
}
}
if (lastProp) {
// 同第一个循环处理style样式
for (styleName in lastProp) {
if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
} // Update styles that changed since `lastProp`.
// 更新style
for (styleName in nextProp) {
if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
// lastProp不存在,就说明是新增style,加入到updateQueue中
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
updatePayload.push(propKey, styleUpdates);
}
styleUpdates = nextProp;
}
}
//...一大堆特殊prop
else {
// 将需要更新的props加入到updateQueue中
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
if (styleUpdates) {
// 将上文处理的styleUpdates加入到updateQueue中
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
//返回出去
return updatePayload;
}
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
//...
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext); // TODO: Type this specific to this type of component.
workInProgress.updateQueue = updatePayload; //将上边处理好的updatePayload 赋值
if (updatePayload) {
markUpdate(workInProgress);
}
}
function markUpdate(workInProgress) {
workInProgress.flags |= Update;
}
通过prepareUpdate函数可以获取到当前workInProgressFiber节点的updatePayload,如果updatePayload存在,也就是节点新旧props存在差异(需要更新),就会调用markUpdate函数,该函数的作用是为Fiber节点的flags加上Update的标签。
completeUnitOfWork构建effectList
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
if ((completedWork.flags & Incomplete) === NoFlags) {//判断completedWork上有副作用,并且没有结束
next = completeWork(current, completedWork, subtreeRenderLanes); //进入到completeWork根据fiber的tag进行DOM增删改。
if (next !== null) {
workInProgress = next;
return;
}
resetChildLanes(completedWork);
if (returnFiber !== null &&
(returnFiber.flags & Incomplete) === NoFlags) {
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;
}
var flags = completedWork.flags;
if (flags > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else {
//....
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null); // We've reached the root.
}
构建effectList大致可以分为两步走:
- 将子Fiber的firstEffect和lastEffect赋值或拼接给父Fiber。
- 将拥有副作用的子节点与父节点的firstEffect和lastEffect串成链表。
第一步
if (returnFiber.firstEffect === null) {// returnFiber的firstEffect为空的时候
//直接将子节点的firstEffect进行赋值。
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {// 子节点有lastEffect,也就是说明子节点的下级是有副作用节点的
//没有副作用节点就没必要走下边的逻辑,构建effectList是为了commit更新使用
if (returnFiber.lastEffect !== null) {// 拥有多个子节点的情况 将子节点的effectList串在父节点上
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;//将父节点的lastEffect指针指在子节点的lastEffect上。
}
第二步
对节点的firstEffect和lastEffect进行赋值,将effect通过nextEffect进行连接。
var flags = completedWork.flags;// 之前beginWork阶段进行达标
if (flags > PerformedWork) {//存在副作用
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
下一步
下一步要进入到commitRoot函数中 开始commit阶段。
function performSyncWorkOnRoot(root) {
//......
commitRoot(root); // commit 入口
//......
return null;
}
总结
递
首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginwork函数。
该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。
当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
归
在“归”阶段会调用completeWork处理Fiber节点。
当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。
如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。
“递”和“归”阶段会交错执行直到“归”到rootFiber。