1 )概述
updateContextProvider
和 updateContextConsumer
createContext
返回的两个组件pushProvider
以及 popProvider
2 ) 源码
定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L1352
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// 在这个更新的过程中,去获取了 workingprogress点type
// 这个type就是我们通过createcontext返回的那个provider对象
// 这个对象上面会有一个属性指向 consumer 那个type
const providerType: ReactProviderType<any> = workInProgress.type; // 这里获得的就是 provider 组件
const context: ReactContext<any> = providerType._context; // 这里获得的 context 就是 consumer
// 拿到前后两个 props
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
// 跳过
if (__DEV__) {
const providerPropTypes = workInProgress.type.propTypes;
if (providerPropTypes) {
checkPropTypes(
providerPropTypes,
newProps,
'prop',
'Context.Provider',
ReactCurrentFiber.getCurrentFiberStackInDev,
);
}
}
// 注意这里
pushProvider(workInProgress, newValue);
if (oldProps !== null) {
const oldValue = oldProps.value;
const changedBits = calculateChangedBits(context, newValue, oldValue);
if (changedBits === 0) {
// 没有更新
// No change. Bailout early if children are the same.
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
// 存在更新
// The context value changed. Search for matching consumers and schedule
// them to update.
propagateContextChange(
workInProgress,
context,
changedBits,
renderExpirationTime,
);
}
}
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
return workInProgress.child;
}
const context: ReactContext = providerType._context;
// packages/react/src/ReactContext.js#L53
// 在 createContext 函数内
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
pushProvider
// packages/react-reconciler/src/ReactFiberNewContext.js#L55
export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
const context: ReactContext<T> = providerFiber.type._context;
// isPrimaryRenderer 来自 ReactFiberHostConfig.js
// 这个值在 dom环境中是 true
if (isPrimaryRenderer) {
// 这个 valueCursor 记录的是 当前这个树下面一共经历了几个provider,它对应的值
// consumer 也就是 context 它的 value 去获取,是通过赋制在它上面的这个 _currentValue 来进行一个获取的
push(valueCursor, context._currentValue, providerFiber);
// 所以, 这跟最终去获取这个 consumer 上面的 context 的时候,跟这个 valueCursor 是没有任何关系的
context._currentValue = nextValue;
if (__DEV__) {
warningWithoutStack(
context._currentRenderer === undefined ||
context._currentRenderer === null ||
context._currentRenderer === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer = rendererSigil;
}
} else {
push(valueCursor, context._currentValue2, providerFiber);
context._currentValue2 = nextValue;
if (__DEV__) {
warningWithoutStack(
context._currentRenderer2 === undefined ||
context._currentRenderer2 === null ||
context._currentRenderer2 === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer2 = rendererSigil;
}
}
}
calculateChangedBits
export function calculateChangedBits<T>(
context: ReactContext<T>,
newValue: T,
oldValue: T,
) {
// Use Object.is to compare the new context value to the old value. Inlined
// Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
// 使用这种方法可更准确判断两个值是否相等, 符合下面条件,被认为是全等的
if (
(oldValue === newValue &&
(oldValue !== 0 || 1 / oldValue === 1 / (newValue: any))) ||
(oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare
) {
// No change 没有变化
return 0;
} else {
// 注意,这里 _calculateChangedBits 这个API未开放,直接忽略即可
const changedBits =
typeof context._calculateChangedBits === 'function'
? context._calculateChangedBits(oldValue, newValue)
: MAX_SIGNED_31_BIT_INT;
if (__DEV__) {
warning(
(changedBits & MAX_SIGNED_31_BIT_INT) === changedBits,
'calculateChangedBits: Expected the return value to be a ' +
'31-bit integer. Instead received: %s',
changedBits,
);
}
return changedBits | 0; // | 0 这里是去除小数部分
}
}
(oldValue !== 0 || 1 / oldValue === 1 / (newValue: any)
表示 -0 !== +0oldValue !== oldValue && newValue !== newValue
表示 NAN !== NANMAX_SIGNED_31_BIT_INT
转换成2进制就是 32 位 的 1MAX_SIGNED_31_BIT_INT
propagateContextChange
export function propagateContextChange(
workInProgress: Fiber,
context: ReactContext<mixed>,
changedBits: number,
renderExpirationTime: ExpirationTime,
): void {
// 拿到 当前 provider 的第一个子节点
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
// 对子节点进行遍历
while (fiber !== null) {
let nextFiber;
// Visit this fiber.
let dependency = fiber.firstContextDependency; // 读取这个属性
// 存在,则进入循环
if (dependency !== null) {
do {
// Check if the context matches.
// changedBits 是32位的二进制数都是1
// 只要 dependency.observedBits 不是0,dependency.observedBits & changedBits 就不是 0
// dependency.context === context 表示遍历过程中的组件是依赖于这个当前的context的
// 如果这个context的变化,那么说明它要重新渲染
// 同时它去判断它提供的这个 observedBits 跟 changedBits 它们是有相交的部分的
// 说明它依赖的部分也变化了, 通过这种方式判断这个组件其实是需要更新了
if (
dependency.context === context &&
(dependency.observedBits & changedBits) !== 0
) {
// Match! Schedule an update on this fiber.
// 这个组件如果需要更新,除非是他自己调用了setState来创建了一个更新
// 不然的话没有外部的方式让他去可以更新这种情况
// 因为我们在 beginWork 的时候开始是要判断每一个组件自己的 expirationTime 的
// 如果那个组件它本身没有创建过更新,那么它的 expirationTime 是 nowork
// 是nowork的话,它就直接跳过更新了,这明显不符合我们这边的一个context的一个需求
// context这边它就遍历到这个节点的时候,发现它依赖这个context,如何更新?
// 通过主动去创建 update,并且设置你的 update.tag 是 ForceUpdate
// 这其实没有state的一个更新,但是你必须要更新
// 因为依赖了这个context,context更新了,所以强制更新一下
// 然后把这个update执行 enqueueUpdate 一下,这样的话
// 在后续要更新到这个组件的时候,它就会发现它上面是有update的, 需要去更新它
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
const update = createUpdate(renderExpirationTime);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
// this render is thrown away. Since it's a race condition, not sure it's
// worth fixing.
enqueueUpdate(fiber, update);
}
// 同时这边创建 update 是不够的,还要对这个 fiber 它的 expirationTime 进行一个操作
// 要看它,如果目前它的 expirationTime 的优先级是要大于我当前正在渲染这次 expirationTime 的
// 那么我就把它的 expirationTime 设置为这次 update,让它在这次渲染过程当中,肯定会被更新到
if (fiber.expirationTime < renderExpirationTime) {
fiber.expirationTime = renderExpirationTime;
}
// 不仅要在这个 workingprogress 上面去设置,我还要对它的 alternate,也就是current也要进行一个设置
// 因为这个东西它们应该是要被同步的
let alternate = fiber.alternate;
if (
alternate !== null &&
alternate.expirationTime < renderExpirationTime
) {
alternate.expirationTime = renderExpirationTime;
}
// Update the child expiration time of all the ancestors, including
// the alternates.
// 因为在这个过程当中给这个组件创建了一个update
// 代表的意思是,我父链上面的每一个节点,它的 childExpirationTime 有可能会被改变
// 同样的要执行一遍,类似于我们在 scheduleWorkToRoot 的时候的做的事情,就是设置它的 childExpirationTime
let node = fiber.return;
while (node !== null) {
alternate = node.alternate;
if (node.childExpirationTime < renderExpirationTime) {
node.childExpirationTime = renderExpirationTime;
if (
alternate !== null &&
alternate.childExpirationTime < renderExpirationTime
) {
alternate.childExpirationTime = renderExpirationTime;
}
} else if (
alternate !== null &&
alternate.childExpirationTime < renderExpirationTime
) {
alternate.childExpirationTime = renderExpirationTime;
} else {
// Neither alternate was updated, which means the rest of the
// ancestor path already has sufficient priority.
break;
}
node = node.return;
}
}
// 最后设置
nextFiber = fiber.child;
dependency = dependency.next; // 从这里可以看出 dependency 它也是可以有多个的
} while (dependency !== null);
} else if (fiber.tag === ContextProvider) {
// Don't scan deeper if this is a matching provider
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else {
// Traverse down.
nextFiber = fiber.child;
}
// 接下去和很多地方差不多的,就是它往子树上去找
// 如果子树上没有了,它就往它的兄弟节点去找
// 就是相当于要把我们的这个provider当前的这个组件,它的子树的每一个节点去遍历到
// 并且找到有 firstContextDependency 这个属性的这些节点
// 给它去创建更新的一个过程
if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
nextFiber.return = fiber;
} else {
// No child. Traverse to next sibling.
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// We're back to the root of this subtree. Exit.
nextFiber = null;
break;
}
let sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
// No more siblings. Traverse up.
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
firstContextDependency
updateContextConsumer
函数中
prepareToReadContext
函数, 在这个函数中 workInProgress.firstContextDependency = null;
readContext
获取到 newValue
lastContextDependency
为 null 的时候
currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
const contextType = ctor.contextType;
声明了一个叫做 contextType 的这么一个属性readContext
updateClassComponent
packages/react-reconciler/src/ReactFiberBeginWork.js#L428
propagateContextChange
这个方法里面可以看到
_currentValue
设置了一个值readcontext
的时候,最后是有一句代码的return isPrimaryRenderer ? context._currentValue : context._currentValue2;