// ReactFiberContext.js
// 老 context 的两种 cursor
const emptyContextObject = {};
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// ReactFiberNewContext.js
// 新 context cursor
const valueCursor: StackCursor<mixed> = createCursor(null);
//
function pushContextProvider(workInProgress: Fiber): boolean {
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyContextObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
// 上一次 push 时的值,即对应父组件的 context
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);
return true;
}
// 组件需要更新
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
// 重新计算新的合并过的 context,(会合并父组件的 context )
const mergedContext = processChildContext(
workInProgress,
type,
previousContext, // 上一次 push 的 context 的值
);
// 挂载到 reactInternalMemoizedMergedChildContext 属性上
instance.reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
// finishClassComponent 之前在 updateClassComponent 的时候 push 了一次老的,如果有更新,需要将老的 pop
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
// 再 push 新计算出来的 context
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
#### processChildContext
- 合并父组件的 `context` ,相同 `key` 属性会被覆盖
```js
function processChildContext(
fiber: Fiber,
type: any,
parentContext: Object, // 全局变量 previousContext
): Object {
const instance = fiber.stateNode;
const childContextTypes = type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
// 如果没有 getChildContext 方法就直接返回父组件的 context
if (typeof instance.getChildContext !== 'function') {
return parentContext;
}
let childContext;
// 获取新的 context
childContext = instance.getChildContext();
// 合并 context
return {...parentContext, ...childContext};
}
function getUnmaskedContext(
workInProgress: Fiber,
Component: Function,
didPushOwnContextIfProvider: boolean, // 读 context 一般都为 true
): Object {
// 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
// 不是 provider 则读当前的 cursor 就可以
// 或者 mountIndeterminateComponent 函数里 didPushOwnContextIfProvider 为 false
return contextStackCursor.current;
}
getMaskedContext
从 context 中获取组件声明的 contextTypes 中需要的数据
function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
// 如果有缓存则从缓存里读
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
// 根据 contextTypes 上声明的 key 读取父组件合并过后的 context 中的值
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
// 缓存 context
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
}
// 缓存
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
if (__DEV__) {
// ....
} else {
context.Consumer = context;
}
if (__DEV__) {
context._currentRenderer = null;
context._currentRenderer2 = null;
}
return context;
}
context
stack 概念
cursor
context
contextStackCursor
: 记录当前组件和父组件一起提供给子组件的childContext
对象,默认是{}
didPerformWorkStackCursor
: 标记子树的 context 是否变化的,false
表示可以跳过更新,记录的值其实是shouldUpdate
valueCursor
: 新context
的 cursorLegacyContext 老版本的 context
父组件提供
getChildContext
方法、声明childContextTypes
属性,子组件声明contextTypes
属性的方式获取context
的问题context provider
会合并key
相同的属性classComponent
才能使用getChildContext
方法给子树提供childContext
updateHostRoot 对 FiberRoot 会执行第一次 context push,
之后只有 classComponent 能提供 childContext, 在 updateClassComponent 中 push 子树的 context 对象,
pushContextProvider
updateComponentClass
时首先push
一次老的context
previousContext
赋值为contextStackCursor.current
即上一次push
的值,这个值记录着父组件提供的context
的集合updateComponentClass
时并不知道组件是否需要更新,并不知道是否有新的 contetxt只有在
finishClassComponent
中判断如果组件需要更新了,会重新计算新的context
,执行invalidateContextProvider
将计算过后的mergedContext
挂载在__reactInternalMemoizedMergedChildContext
属性上invalidateContextProvider
finishClassComponent
中如果不需要更新且没有错误则跳过更新,并且如果hasContext
为true
则会执行invalidateContextProvider
,这里didChange
是false
,即对应push
的didPerformWorkStackCursor
值为false
,表示组件可以跳过更新,在往下执行子树的beginWork
时就会根据!hasContextChanged()
判断是否可以跳过更新finishClassComponent
如果组件需要更新,也会执行invalidateContextProvider
,这里didChange
为true
PureComponent
不会判断context
是否变化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);
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
// 不需要更新且没有错误则跳过更新 if (!shouldUpdate && !didCaptureError) { // Context providers should defer to sCU for rendering // updateClassComponent 中使用了老 context 后会赋值为 true // didChange 为 false if (hasContext) { invalidateContextProvider(workInProgress, Component, false); } // 跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } // ... if (hasContext) { invalidateContextProvider(workInProgress, Component, true); }
return workInProgress.child; }
// invalidateContextProvider
function invalidateContextProvider( workInProgress: Fiber, type: any, didChange: boolean, // 组件是否有更新 ): void { const instance = workInProgress.stateNode;
// 组件需要更新 if (didChange) { // Merge parent and own context. // Skip this if we're not updating due to sCU. // This avoids unnecessarily recomputing memoized values. // 重新计算新的合并过的 context,(会合并父组件的 context ) const mergedContext = processChildContext( workInProgress, type, previousContext, // 上一次 push 的 context 的值 ); // 挂载到 reactInternalMemoizedMergedChildContext 属性上 instance.reactInternalMemoizedMergedChildContext = mergedContext;
} else { // 没有更新,有没有更新都需要将 didPerformWorkStackCursor.current 同步 didChange, 如果不同步 false 的情况那组件 beginWork 时会一直更新,没有可以跳过更新的情况 // pop 老的 pop(didPerformWorkStackCursor, workInProgress); // push 新的 // 仍需要 push didChange 将 didPerformWorkStackCursor.current = false push(didPerformWorkStackCursor, didChange, workInProgress); } }
hasContextChanged
beginWork
开始时会调用这个方法根据context
是否变化来判断是否可以跳过组件更新,这也是影响性能的一个地方!hasLegacyContextChanged()
即为false
时跳过更新,这个值实际存储的是didChange
,跟shouldComponentUpdate
有关context
变化,各个类型的组件分别push context
// beginWork function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { const updateExpirationTime = workInProgress.expirationTime;
// 非首次渲染 if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps === newProps && // 新老 props 相等 !hasLegacyContextChanged() && // 父组件的 context 没有变化 (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) ) { // This fiber does not have any pending work. Bailout without entering // the begin phase. There's still some bookkeeping we that needs to be done // in this optimized path, mostly pushing stuff onto the stack. switch (workInProgress.tag) { case HostRoot: pushHostRootContext(workInProgress); resetHydrationState(); break; case HostComponent: pushHostContext(workInProgress); break; case ClassComponent: { const Component = workInProgress.type; // 有 childContextTypes 属性 if (isLegacyContextProvider(Component)) { // 入栈 pushLegacyContextProvider(workInProgress); } break; } case HostPortal: pushHostContainer( workInProgress, workInProgress.stateNode.containerInfo, ); break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; pushProvider(workInProgress, newValue); break; } case Profiler: if (enableProfilerTimer) { workInProgress.effectTag |= Update; } break; case SuspenseComponent: { // ... break; } } // 跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } // .... }
子组件从父组件提供的 context 对象里获取需要的属性
getUnmaskedContext
获取合并过的context
getMaskedContext
从getUnmaskedContext
的结果里取需要的属性getUnmaskedContext
-
getMaskedContext
context
中获取组件声明的contextTypes
中需要的数据新 context
Context.Provider
Context.Consumer
context
的提供方和订阅方都是独立的ReactContext
context
,并创建Provider
组件类型,Consumer
组件类型即创建的context
ContextConsumer = REACT_CONTEXT_TYPE;
updateContextProvider
pushProvider
的时候设置context._currentValue = nextValue
; 最后consumer
是从这个值中读取的case ContextProvider: return updateContextProvider( current, workInProgress, renderExpirationTime, );
function updateContextProvider( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { const providerType: ReactProviderType = workInProgress.type;
// _context 指向创建的 context 对象(也是 Consumer)
const context: ReactContext = providerType._context;
const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value; // push 新的 value pushProvider(workInProgress, newValue);
if (oldProps !== null) { const oldValue = oldProps.value; // 模拟 Object.is // 如果 newValue, oldValue 相等返回 0 // 如果不相等则返回 MAX_SIGNED_31_BIT_INT const changedBits = calculateChangedBits(context, newValue, oldValue); if (changedBits === 0) { // No change. Bailout early if children are the same. if ( oldProps.children === newProps.children && !hasLegacyContextChanged() ) { // newValue, oldValue相等 且新老 children 相同,且没有老 context变化的条件下,可以跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } else { // The context value changed. Search for matching consumers and schedule // them to update. // 有更新,则遍历子节点对有 firstContextDependency 的 classComponent 类型的节点创建一个 forceUpdate 的更新 propagateContextChange( workInProgress, context, changedBits, renderExpirationTime, ); } }
const newChildren = newProps.children; // 调和子节点 reconcileChildren(current, workInProgress, newChildren, renderExpirationTime); return workInProgress.child; }
propagateContextChange
Provider
组件firstContextDependency
的节点,如果节点依赖的context
有更新(非setState
的更新),对于classComponent
类型主动创建一个tag
为forceUpdate
的update
来强制更新,并操作expirationTime
和父节点的childExpirationTime
firstContextDependency
是在updateContextConsumer
中prepareToReadContext
方法初始化,在readContext
中被赋值updateContextConsumer
workInProgress.type
即createContext
返回的context
对象prepareToReadContext
初始化firstContextDependency
,赋值全局变量currentlyRenderingFiber
为当前workInProgress
function updateContextConsumer( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { let context: ReactContext = workInProgress.type;
const newProps = workInProgress.pendingProps;
// consumer 的 children 为一个函数,这个函数的参数即 provider 提供的 value
const render = newProps.children;
// 初始化 currentlyRenderingFiber 、workInProgress.firstContextDependency prepareToReadContext(workInProgress, renderExpirationTime); // 读取 context._currentValue , const newValue = readContext(context, newProps.unstable_observedBits); let newChildren; if (DEV) { // ... } else { // 调用 render 获取children newChildren = render(newValue); }
// React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; // 调和子节点 reconcileChildren(current, workInProgress, newChildren, renderExpirationTime); return workInProgress.child; }
readContext
firstContextDependency
到lastContextDependency
的 单链表结构context._currentValue
做为context
updateFunctionComponent、updateClassComponent
也会执行prepareToReadContext
这个方法,对于classComponent
组件还可以声明contextType
的方法来获取新context
,在constructClassInstance、mountClassInstance、resumeMountClassInstance、updateClassInstance
方法中会判断contextType
是否存在,如果存在则在赋值instance.context = readContext(contextType)
, 从而能在声明周期方法里通过this.context
获取context