本文主要是介绍React16源码: React中的updateClassComponent的源码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
ClassComponent 的更新
1 ) 概述
- 在 react 中 class component,是一个非常重要的角色
- 它承担了 react 中 更新整个应用的API
setState
forceUpdate
- 在react当中,只有更新了state之后,整个应用才会重新进行渲染
- 在 class component 中, 它的逻辑相对复杂
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 effectworkInProgress.effectTag |= Placement;}// In the initial pass we might need to construct the instance.// 对于没有instance的一个情况,需要去创建 class instanceconstructClassInstance(workInProgress,Component,nextProps,renderExpirationTime,);// 并且要mount这个 class instancemountClassInstance(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 已被创建// 代表着可以复用 instanceshouldUpdate = resumeMountClassInstance(workInProgress,Component,nextProps,renderExpirationTime,);} else {// 对于又有 current 又有 instance的情况,说明组件已经被重新渲染了// 这个 updateClassInstance 方法和 上述 resumeMountClassInstance 类似// 最大的区别是 内部判断 comonentDidUpdate shouldUpdate = updateClassInstance(current,workInProgress,Component,nextProps,renderExpirationTime,);}// 这里最终调用 finishClassComponentreturn 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) {// 跳过 DEVif (__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);// 获取 stateconst 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 updatesReactInstanceMap.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 来去得到一个新的statelet updateQueue = workInProgress.updateQueue;if (updateQueue !== null) {// 这里 计算出新的state 并赋值给 workInProgress.memoizedStateprocessUpdateQueue(workInProgress,updateQueue,newProps,instance,renderExpirationTime,);// 同时更新 instance.stateinstance.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;// 克隆 updateQueuequeue = 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;// 判断是否有 callbackif (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 functionif (__DEV__) {if (debugRenderPhaseSideEffects ||(debugRenderPhaseSideEffectsForStrictMode &&workInProgress.mode & StrictMode)) {payload.call(instance, prevState, nextProps);}}return payload.call(instance, prevState, nextProps);}// State objectreturn payload;}// 这里 a & ~b 表示: a & 除了b之外的所有属性case CaptureUpdate: {workInProgress.effectTag =(workInProgress.effectTag & ~ShouldCapture) | DidCapture; // 这里最终剩下的 只有 DidCapture}// Intentional fallthroughcase UpdateState: {const payload = update.payload;let partialState;if (typeof payload === 'function') {// Updater functionif (__DEV__) {if (debugRenderPhaseSideEffects ||(debugRenderPhaseSideEffectsForStrictMode &&workInProgress.mode & StrictMode)) {payload.call(instance, prevState, nextProps);}}// 如果是 function 则调用 payload 传入之前的参数 计算出 statepartialState = payload.call(instance, prevState, nextProps);} else {// Partial state objectpartialState = 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 上是否有 DidCaptureconst didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;// 在没有更新也没有错误捕获的情况下if (!shouldUpdate && !didCaptureError) {// Context providers should defer to sCU for renderingif (hasContext) {invalidateContextProvider(workInProgress, Component, false);}// 跳过更新return bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);}const instance = workInProgress.stateNode;// RerenderReactCurrentOwner.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 方法,计算出新的 childrennextChildren = 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 {// 正常情况调用 调和 childrenreconcileChildren(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 中继续进行更新的流程 }
-
简单总结
- 以上是更新 class component 的整体流程
- 一开始要使用
constructClassInstance
创建 class instance - 内部会根据不同情况调用不同方法
- 并且要调用
mountClassInstance
来挂载 - 如果是第一次渲染被中断的情况
- 如果存在 instance 则重复使用 instance
- 它调用的生命周期方法仍然是 第一次渲染的生命周期方法
- 也就是
componentDidMount
- 如果不是第一次渲染的情况
- 调用 updateClassInstance 方法
- 这种会最终调用
componentDidUpdate
方法
- 它们中间的流程类似
- 判断各种情况,创建新的 instance 或 复用之前的
- 通过
processUpdateQueue
来获取新的 state - 会把它赋值到 instance 和 fiber 上面进行记录
- 最终都会调用
finishClassComponent
- 这里面做一些错误判断的处理
- 以及是否可以跳过更新的过程
- 还有重新调和子节点 (通用流程)
这篇关于React16源码: React中的updateClassComponent的源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!