当产生更新时,workInProgressTree 的 Fiber 节点有两种方式生成:
re-render- 复用
currentTree的Fiber节点
本文进行了以下探究:
更新时,能否复用 currentTree 的 Fiber 节点这种情况。
beginWork
当调度更新时,会进入到 render 阶段,也就是产生 Fiber 的阶段,此时会调用到 beginWork 方法,该方法中对类组件和函数组件的处理如下:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
){
switch (workInProgress.tag) {
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
}
}
先看 ClassComponent 的产生 Fiber 的处理再看 FunctionComponent 吧。
ClassComponent
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
return nextUnitOfWork;
}
finishClassComponent 方法执行后会返回 Fiber 节点,而是否可复用 current 的 Fiber,取决于 updateClassInstance 方法的执行结果。
来看下 updateClassInstance :
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes,
) {
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
return shouldUpdate;
}
根据 updateClassInstance 方法里,判断是否需要更新的的 2 个条件:
- 是否有
forceUpdate shouldComponentUpdate(checkShouldComponentUpdate)
可知,ClassComponent 的 Fiber 是否可复用,取决于 2 个条件:本次更新是否是调用 forceUpdate 产生的更新和 shouldComponentUpdate 方法中根据 props 判断当次是否需要更新。
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
根据 checkShouldComponentUpdate 的返回可知,当没有设置 shouldComponentUpdate 方法时,其效果等同于设置了 shouldComponentUpdate 但恒返回 true ,也就是 ClassComponent 会受各种因素而无故 re-render 。
下面来看 Functioncomponent 的产生 Fiber 节点的处理吧。
FunctionComponent
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
FunctionComponent 是否可复用 Fiber ,取决于 didReceiveUpdate 这个变量,这个变量在 beginWork 方法中被赋值:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
){
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
// 当优先度不足够时进入此判断
didReceiveUpdate = false;
switch(workInProgress.tag) {
// ...
}
// 有可能会执行到这,如果上面的 switch 没有 return 语句的话
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
}
由以上可知,当 FunctionComponent 的 props,context(当处于 dev 时,需要判断元素类型 type )未变化,且本次更新的优先级足够时,didReceiveUpdate 变量会设置为 false,在接下来的 updateFunctionComponent 方法的执行后会返回可复用的 Fiber 节点。
总结
更新时,workInProgressTree 能否复用 currentTree 的 Fiber 节点取决于:
ClassComponent- 本次更新不是调用
forceUpdate来更新的 shouldComponentUpdate中对更新的属性进行判断来决定本次更新是不需更新的
- 本次更新不是调用
FunctionComponentdev时,元素类型type不变props不变context没有更新- 本次优先级足够
参考
[ beginWork / updateFunctionComponent / updateClassComponent / finishClassComponent] github.com/facebook/re…
[updateClassInstance / checkShouldComponentUpdate ] github.com/facebook/re…
[checkHasForceUpdateAfterProcessing] github.com/facebook/re…