前言:
在上篇 React源码解析之updateClassComponent(上) 中,我们讨论了更新ClassComponent
的第一种情况— —「类实例(class instance)未被创建」的情况。
本文将先讨论第二种情况: 类实例存在,但 current 为 null,即第一次渲染的情况:
//第一次渲染
else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
//此时 instance 已经创建,复用 class 实例,更新 props/state,
// 调用生命周期(componentWillMount,componentDidMount),返回 shouldUpdate
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
一、resumeMountClassInstance
作用:
复用ClassComponent
实例,更新props
和state
,调用生命周期API—componentWillMount()
和componentDidMount()
,最终返回shouldUpdate:boolean
源码:
//复用 class 实例,更新 props/state,调用生命周期,返回 shouldUpdate
function resumeMountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
//获取 ClassComponent 实例
const instance = workInProgress.stateNode;
//获取已有的 props
const oldProps = workInProgress.memoizedProps;
//初始化 类实例 的 props
instance.props = oldProps;
//=====可跳过 context 相关代码====================================================
const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext;
if (typeof contextType === 'object' && contextType !== null) {
nextContext = readContext(contextType);
} else {
const nextLegacyUnmaskedContext = getUnmaskedContext(
workInProgress,
ctor,
true,
);
nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
}
//============================================================================
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
//从开发角度上看,只要有调用getDerivedStateFromProps()或getSnapshotBeforeUpdate()
//其中一个生命周期API,变量 hasNewLifecycles 就为 true
const hasNewLifecycles =
typeof getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function';
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
//如果没有用新的生命周期的方法,则执行componentWillReceiveProps()
//也就是说,如果有getDerivedStateFromProps()或getSnapshotBeforeUpdate(),就不调用componentWillReceiveProps方法了
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext,
);
}
}
//设置 hasForceUpdate 为 false
resetHasForceUpdateBeforeProcessing();
//====更新 updateQueue,获取新 state,与mountClassInstance中的callComponentWillMount的下面逻辑相同,不再赘述================================
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
//====================================
//如果新老 props 和 state 没有差别,并且没有 forceupdate 的情况,
//那么组件就不更新
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
//由于current为 null,即第一次渲染,需要调用componentDidMount()
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
//即 shouldUpdate 为 false
return false;
}
//有调用getDerivedStateFromProps()的话,则执行对应的applyDerivedStateFromProps
//componentWillReceiveProps()与「getDerivedStateFromProps()/getSnapshotBeforeUpdate()」是互斥关系
//这边能执行,说明componentWillReceiveProps()就不执行
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
newState = workInProgress.memoizedState;
}
//检查是否有 forceUpdate 和新老 props/state 的更新
//只有当既没有 forceUpdate 又没有 props/state 的改变,shouldUpdate才会为 false
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
//当有更新的时候,执行相应的生命周期方法——componentWillMount()和componentDidMount()
if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
startPhaseTimer(workInProgress, 'componentWillMount');
if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
instance.UNSAFE_componentWillMount();
}
stopPhaseTimer();
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
//有一个疑问——为什么不需要 update,还要执行componentDidMount方法来更新?
//没明白注释写的cWU/cDU是啥意思
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
// If shouldComponentUpdate returned false, we should still update the
// memoized state to indicate that this work can be reused.
//即使不需要 update,也会更新原有的 props/state,以保证复用
//也没明白为啥
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
//更新相关属性为最新的 props/state,无论是否有 update
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
//boolean
return shouldUpdate;
}
解析:
(1) 如果没有调用getDerivedStateFromProps()
或getSnapshotBeforeUpdate()
的话,
则调用componentWillReceiveProps()
(2) 更新updateQueue
,获取newState
(3) 如果新老props
和state
没有差别,并且没有forceupdate
的情况,那么组件就不更新,shouldUpdate=false
(4) 如果有调用getDerivedStateFromProps()
,则执行它
注意:
与 (1) 相比,则发现,componentWillReceiveProps()
与「getDerivedStateFromProps()
/getSnapshotBeforeUpdate()
」是互斥关系,(4) 能执行,则 (1) 不会执行,反之一样
(5) 执行checkShouldComponentUpdate()
,检查是否有forceUpdate
和新老props
/state
的更新。
只有当既没有forceUpdate
又没有props
/state
的改变,shouldUpdate
才会为false
(6) 当shouldUpdate
为true
时,判断是否执行componentWillMount()
和componentDidMount()
(7) 当shouldUpdate
为false
时,仍会判断执行componentDidMount()
和更新memoizedProps
(8) 更新instance
上的props
和state
(9) 最后返回shouldUpdate
注意:
关于getSnapshotBeforeUpdate()
的作用及用法,请参考:
https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
里面部分function
比较简单,先不讲,接下来讲下checkShouldComponentUpdate()
二、checkShouldComponentUpdate
作用:
检查是否有props
/state
的更新,也是判断是否需要执行shouldComponentUpdate()
的方法
源码:
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
//如果有调用`shouldComponentUpdate()`的话,则返回执行该方法的结果
if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
stopPhaseTimer();
//删除 dev 代码
//返回true/false
return shouldUpdate;
}
//如果是纯组件的话,用**浅比较**来比较 props/state
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
//浅等于的判断
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
解析:
(1) 如果有调用shouldComponentUpdate()
的话,则执行它,并返回执行结果,不再往下继续
(2) 如果是纯组件的话(PureComponent),则执行shallowEqual()
,用 浅比较 来比较props
/state
,返回结果,不再往下继续
(3) 返回true
注意:
关于 PureComponent 的浅比较判断,即shallowEqual()
,将在下篇文章解析。
接下来,我们来谈论最后一种情况: 类实例存在,并且是多次渲染的情况:
//instance!==null&¤t!==null
//当已经创建实例并且不是第一次渲染的话,调用更新的生命周期方法为componentWillUpdate,componentDidUpdate(),
else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
三、updateClassInstance
作用:
当已经创建实例并且不是第一次渲染的话,调用更新的生命周期API— —componentWillUpdate/componentDidUpdate()
源码:
// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
const oldProps = workInProgress.memoizedProps;
instance.props =
workInProgress.type === workInProgress.elementType
? oldProps
: resolveDefaultProps(workInProgress.type, oldProps);
const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext;
if (typeof contextType === 'object' && contextType !== null) {
nextContext = readContext(contextType);
} else {
const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
typeof getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function';
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext,
);
}
}
resetHasForceUpdateBeforeProcessing();
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
//注意这里与 resumeMountClassInstance() 不一样
//updateClassInstance():componentDidUpdate/getSnapshotBeforeUpdate
//resumeMountClassInstance():componentDidMount
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
return false;
}
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
newState = workInProgress.memoizedState;
}
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
//此处也与resumeMountClassInstance() 不同
//updateClassInstance():componentWillUpdate/componentDidUpdate/getSnapshotBeforeUpdate
//resumeMountClassInstance():componentWillMount/componentDidMount
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function')
) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
stopPhaseTimer();
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.effectTag |= Snapshot;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
解析:
该方法与resumeMountClassInstance()
逻辑类似,就不再赘述了,但注意下两者调用生命周期 API 的不同:
在这三种情况执行后,updateFunctionComponent()
最后执行了finishClassComponent()
方法,来判断是否需要render
:
//判断是否执行 render,并返回 render 下的第一个 child
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
接下来,我们就看下该方法
四、finishClassComponent
作用:
判断是否执行render()
,并返回render
下的第一个child
源码:
//判断是否执行 render,并返回 render 下的第一个 child
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
//无论是否更新 props/state,都必须更新 ref 指向
markRef(current, workInProgress);
//判断是否有错误捕获
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
//当不需要更新/更新完毕,并且没有出现 error 的时候
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
//跳过该class 上的节点及所有子节点的更新,即跳过调用 render 方法
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
//getDerivedStateFromError是生命周期api,作用是捕获 render error,详情请看:
//https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// If we captured an error, but getDerivedStateFrom catch is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
//如果出现 error 但是开发者没有调用getDerivedStateFromError的话,就中断渲染
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
}
//否则重新渲染
else {
//删除了 dev 代码
if (__DEV__) {
} else {
nextChildren = instance.render();
}
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
//强制重新计算 children,因为当出错时,是渲染到节点上的 props/state 出现了问题,所以不能复用,必须重新 render
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
} else {
// 将 ReactElement 变成fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}
// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
workInProgress.memoizedState = instance.state;
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
//返回 render 下的第一个节点
return workInProgress.child;
}
解析:
(1) 无论是否更新props
/state
,都必须更新ref
指向,故执行markRef()
:
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
// Schedule a Ref effect
workInProgress.effectTag |= Ref;
}
}
(2) 判断是否有错误捕获,赋值给didCaptureError
(3) 当不需要更新/更新完毕,并且没有捕获到error
的时候,则执行bailoutOnAlreadyFinishedWork()
,跳过该ClassInstance
上的节点及所有子节点的更新,即跳过调用 render
方法
关于bailoutOnAlreadyFinishedWork()
的讲解,请看:
React源码解析之workLoop
(4) 如果捕获到了error
,并且开发者没有调用getDerivedStateFromError
的话,就中断渲染,将nextChildren
置为null
关于getDerivedStateFromError()
的讲解,请看:
https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror
(5) 如果没有捕获到error
的话,则执行instance.render()
,重新渲染,并返回nextChildren
(6) 渲染后,如果捕获到error
,则执行forceUnmountCurrentAndReconcile()
,强制重新计算children
;
否则,执行reconcileChildren()
,将ReactElement
变成fiber
对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
(7) 最后,返回 render 下的第一个节点workInProgress.child
finishClassComponent()
的流程图如下:
OK,至此updateClassComponent()
就讲解结束,关于PureComponent
的浅比较判断,即shallowEqual()
,将在下篇文章解析。
GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberBeginWork.js
(完)