1 ) 概述
setState
forceUpdate
2 )源码
在 packages/react-reconciler/src/ReactFiberBeginWork.js
// 这个方法就是更新 ClassComponent 组件的一个过程
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// 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.
// 先跳过 context 相关的逻辑
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
// instance 就是我们 class component,通过new这个class获取的一个对象
const instance = workInProgress.stateNode;
// 在这里面,它声明了一个 shouldUpdate 的一个属性
let shouldUpdate;
// 先判断instance是否存在, 比如说我们第一次通过 ReactDOM.render 进行渲染的过程当中
// 在从上往下第一次渲染的过程当中,第一次更新到 class component 的时候
// 它的instance肯定是不存在的,因为它还没有被更新过,所以它的节点肯定是没有被创建的
if (instance === null) {
// 接下去, 判断一下current是否等于null
// current等于null的情况是代表在进入第一次渲染,因为current它还不存在
// 如果current不等于null, 代表我们至少已经经历过一次渲染了
// 这时候 instance 不存在,而 current 存在,说明第一次渲染的时候没有创建这个instance
// 同样说明这个组件是被 suspended 的,一个组件处于suspended的状态
// 在这里react认为它相当于是第一次被渲染, 在初次渲染的时候,只是抛出了一个promise
// 并没有真正的渲染出它的子节点, 抛出了一个promise之后,接下去的渲染就结束了
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.
// 对于没有instance的一个情况,需要去创建 class instance
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 并且要mount这个 class instance
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 在第一次渲染的时候,会执行上述两个方法,并且 shouldUpdate = true;
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
// 在 有instance 和 没有 current 的情况下,这种是之前被中断的
// 在执行 class component 的 render 方法的时候 报错,但 instance 已被创建
// 代表着可以复用 instance
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
} else {
// 对于又有 current 又有 instance的情况,说明组件已经被重新渲染了
// 这个 updateClassInstance 方法和 上述 resumeMountClassInstance 类似
// 最大的区别是 内部判断 comonentDidUpdate
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
// 这里最终调用 finishClassComponent
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
进入 constructClassInstance
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
// 先跳过 context 相关
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = null;
const contextType = ctor.contextType;
//
if (typeof contextType === 'object' && contextType !== null) {
// 跳过 DEV
if (__DEV__) {
if (
contextType.$$typeof !== REACT_CONTEXT_TYPE &&
!didWarnAboutInvalidateContextType.has(ctor)
) {
didWarnAboutInvalidateContextType.add(ctor);
warningWithoutStack(
false,
'%s defines an invalid contextType. ' +
'contextType should point to the Context object returned by React.createContext(). ' +
'Did you accidentally pass the Context.Provider instead?',
getComponentName(ctor) || 'Component',
);
}
}
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.
if (__DEV__) {
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
new ctor(props, context); // eslint-disable-line no-new
}
}
// ctor 是 construct 的一个缩写 这个 ctor 就是在 ReactElement当中
// 存在的那个type,也就是 class component 定义的那个class
// 传入 props和context,对应于 ReactBaseClasses.js 里面,Component 函数 接收的这两个参数
// 第三个参数 updater 后面在 adoptClassInstance 里面设置
const instance = new ctor(props, context);
// 获取 state
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
adoptClassInstance(workInProgress, instance);
if (__DEV__) {
if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) {
const componentName = getComponentName(ctor) || 'Component';
if (!didWarnAboutUninitializedState.has(componentName)) {
didWarnAboutUninitializedState.add(componentName);
warningWithoutStack(
false,
'`%s` uses `getDerivedStateFromProps` but its initial state is ' +
'%s. This is not recommended. Instead, define the initial state by ' +
'assigning an object to `this.state` in the constructor of `%s`. ' +
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
componentName,
instance.state === null ? 'null' : 'undefined',
componentName,
);
}
}
// If new component APIs are defined, "unsafe" lifecycles won't be called.
// Warn about these lifecycles if they are present.
// Don't warn about react-lifecycles-compat polyfilled methods though.
if (
typeof ctor.getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function'
) {
let foundWillMountName = null;
let foundWillReceivePropsName = null;
let foundWillUpdateName = null;
if (
typeof instance.componentWillMount === 'function' &&
instance.componentWillMount.__suppressDeprecationWarning !== true
) {
foundWillMountName = 'componentWillMount';
} else if (typeof instance.UNSAFE_componentWillMount === 'function') {
foundWillMountName = 'UNSAFE_componentWillMount';
}
if (
typeof instance.componentWillReceiveProps === 'function' &&
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
) {
foundWillReceivePropsName = 'componentWillReceiveProps';
} else if (
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
) {
foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
}
if (
typeof instance.componentWillUpdate === 'function' &&
instance.componentWillUpdate.__suppressDeprecationWarning !== true
) {
foundWillUpdateName = 'componentWillUpdate';
} else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
foundWillUpdateName = 'UNSAFE_componentWillUpdate';
}
if (
foundWillMountName !== null ||
foundWillReceivePropsName !== null ||
foundWillUpdateName !== null
) {
const componentName = getComponentName(ctor) || 'Component';
const newApiName =
typeof ctor.getDerivedStateFromProps === 'function'
? 'getDerivedStateFromProps()'
: 'getSnapshotBeforeUpdate()';
if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
warningWithoutStack(
false,
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
'%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
'The above lifecycles should be removed. Learn more about this warning here:\n' +
'https://fb.me/react-async-component-lifecycle-hooks',
componentName,
newApiName,
foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
foundWillReceivePropsName !== null
? `\n ${foundWillReceivePropsName}`
: '',
foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
);
}
}
}
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
adoptClassInstance
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater; // 挂载 update 方法
workInProgress.stateNode = instance; // instance 挂载到 stateNode 以供下次进来时使用,下次进来就会存在了
// The instance needs access to the fiber so that it can schedule updates
ReactInstanceMap.set(instance, workInProgress);
if (__DEV__) {
instance._reactInternalInstance = fakeInternalInstance;
}
}
ReactInstanceMap
这个就是对 instance 上 _reactInternalFiber
属性的设置export function remove(key) {
key._reactInternalFiber = undefined;
}
export function get(key) {
return key._reactInternalFiber;
}
export function has(key) {
return key._reactInternalFiber !== undefined;
}
export function set(key, value) {
key._reactInternalFiber = value;
}
this._reactInternalFiber
就可以拿到当前 fiber 对象进入 mountClassInstance
// Invokes the mount life-cycles on a previously never rendered instance.
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
}
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
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);
}
if (__DEV__) {
if (instance.state === newProps) {
const componentName = getComponentName(ctor) || 'Component';
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
didWarnAboutDirectlyAssigningPropsToState.add(componentName);
warningWithoutStack(
false,
'%s: It is not recommended to assign props directly to state ' +
"because updates to props won't be reflected in state. " +
'In most cases, it is better to use props directly.',
componentName,
);
}
}
if (workInProgress.mode & StrictMode) {
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
workInProgress,
instance,
);
ReactStrictModeWarnings.recordLegacyContextWarning(
workInProgress,
instance,
);
}
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.recordDeprecationWarnings(
workInProgress,
instance,
);
}
}
// 获取 updateQueue,初次渲染这个 updateQueue 是空的
// 对于有 setState 的情况,它的 updateQueen 里面可能有多个update
// 这个时候, 需要调用这个 updateQueen 来去得到一个新的state
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
// 这里 计算出新的state 并赋值给 workInProgress.memoizedState
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
// 同时更新 instance.state
instance.state = workInProgress.memoizedState;
}
// 判断是否有这个生命周期方法
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
// 如果存在该生命周期方法,则计算新的state
// 这个生命周期会在组件更新的过程中被调用
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 生命周期方法
// 在这个生命周期方法中,会执行 setState 方法
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.
// 执行了 这个生命周期方法,就需要重新执行 updateQueue
// 如果在这时执行了 setState, 立马被反映到组件上面
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
processUpdateQueue
export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;
// 克隆 updateQueue
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.
let update = queue.firstUpdate;
let resultState = newBaseState;
while (update !== null) {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) {
// This update does not have sufficient priority. Skip it.
if (newFirstUpdate === null) {
// This is the first skipped update. It will be the first update in
// the new list.
newFirstUpdate = update;
// Since this is the first update that was skipped, the current result
// is the new base state.
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;
// 判断是否有 callback
if (callback !== null) {
workInProgress.effectTag |= Callback; // 加上 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.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;
}
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.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.
workInProgress.expirationTime = newExpirationTime;
workInProgress.memoizedState = resultState;
if (__DEV__) {
currentlyProcessingQueue = null;
}
}
ensureWorkInProgressQueueIsAClone
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) {
// 要保证 workInProgress.updateQueue 是一个克隆的queue, 而非直接进行修改
queue = workInProgress.updateQueue = cloneUpdateQueue(queue); // 拷贝 queue
}
}
return queue;
}
getStateFromUpdate
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
case ReplaceState: {
const payload = update.payload;
if (typeof payload === 'function') {
// Updater function
if (__DEV__) {
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
payload.call(instance, prevState, nextProps);
}
}
return payload.call(instance, prevState, nextProps);
}
// State object
return payload;
}
// 这里 a & ~b 表示: a & 除了b之外的所有属性
case CaptureUpdate: {
workInProgress.effectTag =
(workInProgress.effectTag & ~ShouldCapture) | DidCapture; // 这里最终剩下的 只有 DidCapture
}
// Intentional fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
if (__DEV__) {
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
payload.call(instance, prevState, nextProps);
}
}
// 如果是 function 则调用 payload 传入之前的参数 计算出 state
partialState = payload.call(instance, prevState, nextProps);
} else {
// Partial state object
partialState = payload;
}
// 处理特殊情况
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
// 合并
return Object.assign({}, prevState, partialState);
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
进入 finishClassComponent
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
// 这个先跳过
markRef(current, workInProgress);
// 判断 workInProgress.effectTag 上是否有 DidCapture
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
// 在没有更新也没有错误捕获的情况下
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
// 跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
// 有任何错误捕获, 但没有配置这个api的时候
// 这个 getDerivedStateFromError 生命周期的api和 componentDidCatch 有区别, 后者在下次更新的时候处理,中间可能instance不存在(因为出错了)
// 就继续可能引发在这个class component上面设置的 ref拿到一个null, 操作ref的时候就会出现错误
// 在前者这个新的生命周期的api中, 在本次渲染中,生成 state, 渲染出属性, 这样对于 instance 对象依然存在 对应 ref 就可以拿到实际的对象
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.
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
if (__DEV__) {
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = instance.render();
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
instance.render();
}
ReactCurrentFiber.setCurrentPhase(null);
} else {
// 调用 render 方法,计算出新的 children
nextChildren = instance.render();
}
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork; // 增加 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.
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
} else {
// 正常情况调用 调和 children
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);
}
return workInProgress.child; // 把 render 方法渲染出来的第一个子节点 返回给 nextUnitOfWork, 就可以在 workLoop 中继续进行更新的流程
}
简单总结
constructClassInstance
创建 class instancemountClassInstance
来挂载componentDidMount
componentDidUpdate
方法processUpdateQueue
来获取新的 statefinishClassComponent