前言:
本篇文章给大家带来的是updateClassComponent ()
的讲解,即 ClassComponet 的更新流程:
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,
renderExpirationTime,
);
}
一、updateClassComponent 作用: 更新ClassComponent
源码:
//更新ClassComponent
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
//删除了 dev 代码
//=============context 相关代码,可跳过=========================================================
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
//=====================================================================
// 此处的stateNode指的是ClassComponent对应的Class实例。
// FunctionComponent没有实例,所以stateNode值为null
const instance = workInProgress.stateNode;
let shouldUpdate;
//当未创建实例的时候
if (instance === null) {
//current和workInProgress是doubleBuffer的关系,
//React会先创建workInProgress,在渲染结束后,会把workInProgress复制给 current,此时渲染结束
//渲染了但是没有实例的情况,比如报错时
if (current !== null) {
// An class component without an instance only mounts if it suspended
// inside a non- concurrent tree, in an inconsistent state. We want to
// tree it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial pass we might need to construct the instance.
//构建 class 实例
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
//在未render的 class 实例上调用挂载生命周期
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
}
//第一次渲染
else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
//复用 class 实例,更新 props/state,
// 调用生命周期(componentWillMount,componentDidMount),返回 shouldUpdate
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
//instance!==null&¤t!==null
//当已经创建实例并且不是第一次渲染的话,调用更新的生命周期方法为componentWillUpdate,componentDidUpdate(),
else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
//判断是否执行 render,并返回 render 下的第一个 child
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
//删除了 dev 代码
return nextUnitOfWork;
}
解析:
ClassComponet
的更新流程图如上,主要分三种情况:
(1) 类实例(class instance
)未被创建的情况
(2) 类实例存在,但 current 为 null,即第一次渲染的情况
(3) 类实例存在,并且是多次渲染的情况
上述的三种情况返回的结果会赋值给变量shouldUpdate
(4) 执行finishClassComponent()
方法,判断ClassComponent
是否需要执行render()
方法
接下来看第一种情况:instance = null
二、constructClassInstance 作用: 构建 Class Instance
源码:
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = null;
const contextType = ctor.contextType;
//删除了 dev 代码
//=============context 部分可跳过====================================================
if (typeof contextType === 'object' && contextType !== null) {
context = readContext((contextType: any));
} else {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
const contextTypes = ctor.contextTypes;
isLegacyContextConsumer =
contextTypes !== null && contextTypes !== undefined;
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
}
//==========================================================================
// Instantiate twice to help detect side-effects.
//删除了 dev 代码
//ctor即workInProgress.type,也就是定义classComponet的类
const instance = new ctor(props, context);
// instance.state 即开发层面的 this.state
// 注意这个写法,连等赋值
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
// 初始化 class 实例,即初始化workInProgress和instance
adoptClassInstance(workInProgress, instance);
//删除了 dev 代码
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
//context 相关,可跳过
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
解析:
(1) 注意下ctor
即workInProgress.type
,也就是 ClassComponent 的类
(2) 该方法主要是执行了adoptClassInstance()
方法,接下来讲下这方法
三、adoptClassInstance 作用: 初始化 class instance
源码:
//初始化 class 实例,即初始化workInProgress和instance
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
//ReactInstanceMap.set(instance, workInProgress)
setInstance(instance, workInProgress);
if (__DEV__) {
instance._reactInternalInstance = fakeInternalInstance;
}
}
解析:
adoptClassInstance
主要做了三件事:
(1) 将classComponent
初始化的时候拿到的update
对象赋值给instance.updater
(2) 将新的 ClassComponent 实例赋值给workInProgress.stateNode
(3) 执行setInstance()
方法,将workInProgress
赋值给instance._reactInternalFiber
,这样就能通过instance
即this找到workInProgress
:
// setInstance 即 set
export function set(key, value) {
//即 instance._reactInternalFiber=workInProgress
key._reactInternalFiber = value;
}
注意:
关于classComponentUpdater
对象的讲解, 请看:
React源码解析之setState和forceUpdate
四、mountClassInstance
作用:
在未render
的class
实例上调用挂载生命周期
源码:
// Invokes the mount life-cycles on a previously never rendered instance.
//在未 render 的 class 实例上调用挂载生命周期
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
//nextProps,待更新的 props
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
}
//更新 props/state
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
//=========context 相关,可跳过==================================
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
instance.context = readContext(contextType);
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
}
//=============================================
//删除了 dev 分支
let updateQueue = workInProgress.updateQueue;
//执行更新 update队列
if (updateQueue !== null) {
//里面还更新了 state
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
//因为 state 更新了,所以instance的state也要更新
instance.state = workInProgress.memoizedState;
}
//getDerivedStateFromProps是 React 新的生命周期的方法
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
instance.state = workInProgress.memoizedState;
}
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
//判断是否要调用 componentWillMount
//第一次渲染的话,是要调用componentWillMount的
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
//在ComponentWillMount中是有可能执行 setState 的,
// 所以 React 也要及时更新state 并更新到instance上
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
}
//等到真正渲染到 DOM 上去的时候,再去调用componentDidMount
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
解析:
mountClassInstance()
的逻辑如下:
(1) 初始化 props 和 state
(2) 如果有更新队列的话,执行processUpdateQueue()
并更新 state
从开发角度看应该是根据constructor(){ }
里面的内容,将新的 update push 进 updateQueue,并更新一次 props 和 state
constructor(props) {
super(props);
this.state = {
};
}
(3) 如果开发代码中有执行getDerivedStateFromProps()
的话,则调用对应的applyDerivedStateFromProps()
API,更新 state
关于getDerivedStateFromProps()
的作用,请查看:
https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromprops
(4) 如果开发代码中有执行componentWillMount()
的话,则调用对应的callComponentWillMount()
API,并且如果开发代码中的componentWillMount(){ }
里面有setState()
方法,导致updateQueue
里有更新时,执行processUpdateQueue
,更新 props 和 state
(5) 最后,等到要真正渲染到 DOM 上去的时候,再去调用componentDidMount()
接下来讲解下processUpdateQueue()
,看看 ClassComponent 是如何更新 updateQueue 的
五、processUpdateQueue 作用: 更新 update 队列,并更新 state
源码:
//更新 update 队列,并更新 state
export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
//并不是强制的更新
hasForceUpdate = false;
//保证workInProgress上的update队列是 queue 的副本
queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
if (__DEV__) {
currentlyProcessingQueue = queue;
}
// These values may change as we process the queue.
//当执行更新队列的时候,这些属性可能会动态改变,所以先创建副本变量
let newBaseState = queue.baseState;
let newFirstUpdate = null;
let newExpirationTime = NoWork;
// Iterate through the list of updates to compute the result.
//获取队列中的第一个 update元素,来判断它是否需要更新
let update = queue.firstUpdate;
let resultState = newBaseState;
//如果有update的话
while (update !== null) {
//获取该 update 的优先级,判断是否需要执行 update
const updateExpirationTime = update.expirationTime;
//当更新队列的第一个 update元素 的更新优先级低于renderExpirationTime的时候
if (updateExpirationTime < renderExpirationTime) {
// This update does not have sufficient priority. Skip it.
//不执行该 update元素 的更新
if (newFirstUpdate === null) {
// This is the first skipped update. It will be the first update in
// the new list.
//本次没有更新的 update元素,会优先放到下一次去判断要不要更新
newFirstUpdate = update;
// Since this is the first update that was skipped, the current result
// is the new base state.
//如果 update元素 被跳过的话,base state也会改变,所以要及时更新newBaseState
newBaseState = resultState;
}
// Since this update will remain in the list, update the remaining
// expiration time.
//该 update元素 被跳过,仍留在队列中,所以它仍有expirationTime,需要被更新
if (newExpirationTime < updateExpirationTime) {
newExpirationTime = updateExpirationTime;
}
}
//该update元素 会被执行更新的话
else {
// This update does have sufficient priority.
// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reverseable value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
//可跳过
markRenderEventTimeAndConfig(updateExpirationTime, update.suspenseConfig);
// Process it and compute a new result.
//执行 update 并计算出一个新的结果
//获取最新的 state
resultState = getStateFromUpdate(
workInProgress,
queue,
update,
resultState,
props,
instance,
);
//callback 也就是 this.setState({xx:yy},()=>{})的回调函数()=>{}
const callback = update.callback;
//当 callback 不为 null 时,在 setState 更新完后,是要执行 callback 的
//所以要设置相关的属性来“提醒”
if (callback !== null) {
workInProgress.effectTag |= Callback;
// Set this to null, in case it was mutated during an aborted render.
update.nextEffect = null;
//链表的插入操作
if (queue.lastEffect === null) {
queue.firstEffect = queue.lastEffect = update;
} else {
queue.lastEffect.nextEffect = update;
queue.lastEffect = update;
}
}
}
// Continue to the next update.
//跳到下一个 update 元素,循环
update = update.next;
}
//======逻辑同上,不再赘述===============================================
// Separately, iterate though the list of captured updates.
//应该是捕获错误阶段的更新
let newFirstCapturedUpdate = null;
update = queue.firstCapturedUpdate;
while (update !== null) {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) {
// This update does not have sufficient priority. Skip it.
if (newFirstCapturedUpdate === null) {
// This is the first skipped captured update. It will be the first
// update in the new list.
newFirstCapturedUpdate = update;
// If this is the first update that was skipped, the current result is
// the new base state.
if (newFirstUpdate === null) {
newBaseState = resultState;
}
}
// Since this update will remain in the list, update the remaining
// expiration time.
if (newExpirationTime < updateExpirationTime) {
newExpirationTime = updateExpirationTime;
}
} else {
// This update does have sufficient priority. Process it and compute
// a new result.
resultState = getStateFromUpdate(
workInProgress,
queue,
update,
resultState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.effectTag |= Callback;
// Set this to null, in case it was mutated during an aborted render.
update.nextEffect = null;
if (queue.lastCapturedEffect === null) {
queue.firstCapturedEffect = queue.lastCapturedEffect = update;
} else {
queue.lastCapturedEffect.nextEffect = update;
queue.lastCapturedEffect = update;
}
}
}
update = update.next;
}
//======================================================
//这边主要是执行完 update 后,更新 queue 上的相关属性
if (newFirstUpdate === null) {
queue.lastUpdate = null;
}
if (newFirstCapturedUpdate === null) {
queue.lastCapturedUpdate = null;
} else {
workInProgress.effectTag |= Callback;
}
if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
// We processed every update, without skipping. That means the new base
// state is the same as the result state.
newBaseState = resultState;
}
//最后更新 queue 上的属性
queue.baseState = newBaseState;
queue.firstUpdate = newFirstUpdate;
queue.firstCapturedUpdate = newFirstCapturedUpdate;
// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
// 由于执行了 update 队列的部分更新,
// 那么 update 队列的expirationTime将由保留下来的 update 元素的最高优先级的 expirationTime 决定
workInProgress.expirationTime = newExpirationTime;
workInProgress.memoizedState = resultState;
if (__DEV__) {
currentlyProcessingQueue = null;
}
}
解析:
(1) 执行ensureWorkInProgressQueueIsAClone()
,生成updateQueue
的副本queue
(2) 取出queue
的第一个update 元素,并根据它的expirationTime
判断是否需要执行更新
(3) 如果不需要执行更新,则该 update 元素会保留在queue
中,并更新它的expirationTime
(4) 如果需要执行更新的话,执行getStateFromUpdate()
,来获取新的state
(5)如果该 update 元素上,还有 callback 的话(即开发角度的this.setState({xx:yy},()=>{})
的回调函数()=>{}
),还要设置相关属性来“提醒”更新 state 后,再执行 callback
(6) update = update.next
,跳到下一个 update 元素,重复执行 (2)、(3)、(4)、(5)
(7) 然后是「捕获错误」阶段的更新,逻辑同上,不再赘述 (8) 最后,更新 queue 和 workInProgress 上的属性
接下来讲解下ensureWorkInProgressQueueIsAClone()
和getStateFromUpdate()
六、ensureWorkInProgressQueueIsAClone
作用:
保证workInProgress
上的update
队列是queue
的副本
源码:
//保证workInProgress上的update队列是 queue 的副本
function ensureWorkInProgressQueueIsAClone<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
): UpdateQueue<State> {
const current = workInProgress.alternate;
if (current !== null) {
// If the work-in-progress queue is equal to the current queue,
// we need to clone it first.
if (queue === current.updateQueue) {
queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
}
}
return queue;
}
解析:
源码比较简单,就不讲了,cloneUpdateQueue()
的源码如下:
//创建更新队列的副本
function cloneUpdateQueue<State>(
currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState: currentQueue.baseState,
//首、尾之间的update用next串联
firstUpdate: currentQueue.firstUpdate,
lastUpdate: currentQueue.lastUpdate,
// TODO: With resuming, if we bail out and resuse the child tree, we should
// keep these effects.
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
七、getStateFromUpdate
作用:
获取最新的state
源码:
//获取最新的state
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
//返回执行payload后的 state
case ReplaceState: {
const payload = update.payload;
if (typeof payload === 'function') {
// Updater function
//删除了 dev 代码
const nextState = payload.call(instance, prevState, nextProps);
//删除了 dev 代码
return nextState;
}
// State object
return payload;
}
case CaptureUpdate: {
workInProgress.effectTag =
//~ 的意思是获取除了ShouldCapture的所有属性,也就是获取了DidCapture
//因此最后仍是获取了DidCapture
(workInProgress.effectTag & ~ShouldCapture) | DidCapture;
}
// Intentional fallthrough
//通过 setState 传入的属性
case UpdateState: {
const payload = update.payload;
let partialState;
//如果payload是 function 就获取执行payload后得到的 state
if (typeof payload === 'function') {
// Updater function
//删除了 dev 代码
partialState = payload.call(instance, prevState, nextProps);
//删除了 dev 代码
}
//否则就直接赋值给 state
else {
// Partial state object
partialState = payload;
}
//如果partialState没有值,则视为没有更新 state
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
//如果partialState有值的话,需要和未更新的部分 state 属性进行合并
return Object.assign({}, prevState, partialState);
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
解析:
重点看UpdateState
的情况,该情况也是开发角度上this.setState({xxx})
的情况,如果有新state
的话,则执行Object.assign({}, prevState, partialState)
来进行state
的合并
最后:
本文着重讲解了updateClassComponent
的第一种情况— —「类实例(class instance
)未被创建」的情况,第二三种情况会在后续文章中更新
GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberBeginWork.js
(完)