跳到主要内容

非遗留上下文

一、作用

二、重置上下文依赖

export function resetContextDependencies(): void {
// This is called right before React yields execution, to ensure `readContext`
// cannot be called outside the render phase.

// 这会在 React 让出执行权之前调用,以确保 `readContext`
// 不会在渲染阶段之外被调用。
currentlyRenderingFiber = null;
lastContextDependency = null;
if (__DEV__) {
isDisallowedContextReadInDEV = false;
}
}

三、在开发环境中读取不允许的上下文

export function enterDisallowedContextReadInDEV(): void {
if (__DEV__) {
isDisallowedContextReadInDEV = true;
}
}

四、在开发环境中读取不允许的上下文退出

export function exitDisallowedContextReadInDEV(): void {
if (__DEV__) {
isDisallowedContextReadInDEV = false;
}
}

五、推送提供者

export function pushProvider<T>(
providerFiber: Fiber,
context: ReactContext<T>,
nextValue: T,
): void {
if (isPrimaryRenderer) {
push(valueCursor, context._currentValue, providerFiber);

context._currentValue = nextValue;
if (__DEV__) {
push(rendererCursorDEV, context._currentRenderer, providerFiber);

if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'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__) {
push(renderer2CursorDEV, context._currentRenderer2, providerFiber);

if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
}

六、弹出提供者

export function popProvider(
context: ReactContext<any>,
providerFiber: Fiber,
): void {
const currentValue = valueCursor.current;

if (isPrimaryRenderer) {
context._currentValue = currentValue;
if (__DEV__) {
const currentRenderer = rendererCursorDEV.current;
pop(rendererCursorDEV, providerFiber);
context._currentRenderer = currentRenderer;
}
} else {
context._currentValue2 = currentValue;
if (__DEV__) {
const currentRenderer2 = renderer2CursorDEV.current;
pop(renderer2CursorDEV, providerFiber);
context._currentRenderer2 = currentRenderer2;
}
}

pop(valueCursor, providerFiber);
}

七、在父路径上调度上下文工作

备注
export function scheduleContextWorkOnParentPath(
parent: Fiber | null,
renderLanes: Lanes,
propagationRoot: Fiber,
) {
// Update the child lanes of all the ancestors, including the alternates.
// 更新所有祖先的 Lane ,包括备用 Lane。
let node = parent;
while (node !== null) {
const alternate = node.alternate;
if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
node.childLanes = mergeLanes(node.childLanes, renderLanes);
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
}
} else if (
alternate !== null &&
!isSubsetOfLanes(alternate.childLanes, renderLanes)
) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
} else {
// Neither alternate was updated.
// Normally, this would mean that the rest of the
// ancestor path already has sufficient priority.
// However, this is not necessarily true inside offscreen
// or fallback trees because childLanes may be inconsistent
// with the surroundings. This is why we continue the loop.
//
// 两个备用版本都没有更新。
// 通常,这意味着其余的祖先路径已经有足够的优先级。
// 然而,在屏幕外或备用树中情况并非如此,因为 childLanes 可能与周围环境不一致。
// 这就是我们继续循环的原因。
}
if (node === propagationRoot) {
break;
}
node = node.return;
}
if (__DEV__) {
if (node !== propagationRoot) {
console.error(
'Expected to find the propagation root when scheduling context work. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
}
}

八、传播上下文变化

export function propagateContextChange<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
// TODO: This path is only used by Cache components. Update
// lazilyPropagateParentContextChanges to look for Cache components so they
// can take advantage of lazy propagation.
const forcePropagateEntireTree = true;
propagateContextChanges(
workInProgress,
[context],
renderLanes,
forcePropagateEntireTree,
);
}

九、懒惰地传播父上下文的更改

export function lazilyPropagateParentContextChanges(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const forcePropagateEntireTree = false;
propagateParentContextChanges(
current,
workInProgress,
renderLanes,
forcePropagateEntireTree,
);
}

十、将父上下文更改传播到延迟树

// Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
// to the entire subtree, because we won't revisit it until after the current
// render has completed, at which point we'll have lost track of which providers
// have changed.
//
// 用于传播延迟树(Suspense,Offscreen)。我们必须将其传播到整个子树,因为在当前渲染完成之前
// 我们不会重新访问它,届时我们将无法跟踪哪些提供者已经发生了变化。
export function propagateParentContextChangesToDeferredTree(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const forcePropagateEntireTree = true;
propagateParentContextChanges(
current,
workInProgress,
renderLanes,
forcePropagateEntireTree,
);
}

十一、检查上下文是否已更改

export function checkIfContextChanged(
currentDependencies: Dependencies,
): boolean {
// Iterate over the current dependencies to see if something changed. This
// only gets called if props and state has already bailed out, so it's a
// relatively uncommon path, except at the root of a changed subtree.
// Alternatively, we could move these comparisons into `readContext`, but
// that's a much hotter path, so I think this is an appropriate trade off.
//
// 遍历当前的依赖项,查看是否有变化。只有在 props 和 state 已经退出后才会调用,所以这
// 是一个相对不常见的路径,除了在已更改子树的根部。
// 另一种方法是将这些比较移动到 `readContext` 中,但那是一个更高频的路径,所以我
// 认为这是一个适当的权衡。
let dependency = currentDependencies.firstContext;
while (dependency !== null) {
const context = dependency.context;
const newValue = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
const oldValue = dependency.memoizedValue;
if (!is(newValue, oldValue)) {
return true;
}
dependency = dependency.next;
}
return false;
}

十二、准备读取上下文

export function prepareToReadContext(
workInProgress: Fiber,
renderLanes: Lanes,
): void {
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;

const dependencies = workInProgress.dependencies;
if (dependencies !== null) {
// 重置正在进行的工作列表
// Reset the work-in-progress list
dependencies.firstContext = null;
}
}

十三、读取上下文

export function readContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
// This warning would fire if you read context inside a Hook like useMemo.
// Unlike the class check below, it's not enforced in production for perf.
//
// 如果你在像 useMemo 这样的 Hook 内读取 context,就会触发这个警告。
// 与下面的类检查不同,为了性能考虑,这在生产环境中不会强制执行。
if (isDisallowedContextReadInDEV) {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
}
return readContextForConsumer(currentlyRenderingFiber, context);
}

十四、在对账期间读取上下文

export function readContextDuringReconciliation<T>(
consumer: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): T {
if (currentlyRenderingFiber === null) {
prepareToReadContext(consumer, renderLanes);
}
return readContextForConsumer(consumer, context);
}

十五、变量

1. 当前的指针

// 渲染指针
const valueCursor: StackCursor<mixed> = createCursor(null);

// 渲染器指针 (测试)
let rendererCursorDEV: StackCursor<Object | null>;
if (__DEV__) {
rendererCursorDEV = createCursor(null);
}

// 渲染器指针 2 (测试)
let renderer2CursorDEV: StackCursor<Object | null>;
if (__DEV__) {
renderer2CursorDEV = createCursor(null);
}

2. 渲染器符号

let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
// 使用此方法检测多个渲染器是否使用相同的上下文
rendererSigil = {};
}

3. 当前渲染的 Fiber

let currentlyRenderingFiber: Fiber | null = null;

4. 上一次上下文依赖

let lastContextDependency: ContextDependency<mixed> | null = null;

5. 在开发环境中禁止读取的上下文

let isDisallowedContextReadInDEV: boolean = false;

十六、工具

1. 传播上下文变化

备注
function propagateContextChanges<T>(
workInProgress: Fiber,
contexts: Array<any>,
renderLanes: Lanes,
forcePropagateEntireTree: boolean,
): void {
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
// 将子节点的返回指针设置为正在处理的 fiber。
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;

// Visit this fiber.
// 访问这个 fiber。
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;

let dep = list.firstContext;
findChangedDep: while (dep !== null) {
// Assigning these to constants to help Flow
// 将这些分配给常量以帮助 Flow
const dependency = dep;
const consumer = fiber;
findContext: for (let i = 0; i < contexts.length; i++) {
const context: ReactContext<T> = contexts[i];
// Check if the context matches.
// 检查上下文是否匹配。
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
// 匹配!安排对这条光纤的更新。

// In the lazy implementation, don't mark a dirty flag on the
// dependency itself. Not all changes are propagated, so we can't
// rely on the propagation function alone to determine whether
// something has changed; the consumer will check. In the future, we
// could add back a dirty flag as an optimization to avoid double
// checking, but until we have selectors it's not really worth
// the trouble.
//
// 在懒惰实现中,不要在依赖项本身上标记脏标志。并非所有更改都会被传播,所以
// 我们不能仅依靠传播函数来判断某些东西是否已更改;消费者会进行检查。将来,我们
// 可以作为一种优化再次添加脏标志以避免重复检查,但在拥有选择器之前,这并不值得麻烦。
consumer.lanes = mergeLanes(consumer.lanes, renderLanes);
const alternate = consumer.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(
consumer.return,
renderLanes,
workInProgress,
);

if (!forcePropagateEntireTree) {
// During lazy propagation, when we find a match, we can defer
// propagating changes to the children, because we're going to
// visit them during render. We should continue propagating the
// siblings, though
//
// 在懒传播期间,当我们找到匹配项时,我们可以推迟将更改传播到子节点,因为
// 我们将在渲染时访问它们。不过,我们应该继续传播兄弟节点的更改
nextFiber = null;
}

// Since we already found a match, we can stop traversing the
// dependency list.
// 由于我们已经找到匹配项,因此可以停止遍历依赖列表。
break findChangedDep;
}
}
dep = dependency.next;
}
} else if (fiber.tag === DehydratedFragment) {
// If a dehydrated suspense boundary is in this subtree, we don't know
// if it will have any context consumers in it. The best we can do is
// mark it as having updates.
//
// 如果这个子树中有一个脱水的 suspense 边界,我们无法确定
// 它是否会有任何上下文使用者。我们能做的最好的就是
// 将其标记为有更新。
const parentSuspense = fiber.return;

if (parentSuspense === null) {
throw new Error(
'We just came from a parent so we must have had a parent. This is a bug in React.',
);
}

parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
const alternate = parentSuspense.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// This is intentionally passing this fiber as the parent
// because we want to schedule this fiber as having work
// on its children. We'll use the childLanes on
// this fiber to indicate that a context has changed.
//
// 这是故意把这个 fiber 作为父节点传递
// 因为我们想调度这个 fiber,使其子节点有工作要做。
// 我们将使用这个 fiber 的 childLanes 来表示上下文已更改。
scheduleContextWorkOnParentPath(
parentSuspense,
renderLanes,
workInProgress,
);
nextFiber = null;
} else {
// Traverse down.
// 向下遍历。
nextFiber = fiber.child;
}

if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
// 将子节点的返回指针设置为正在处理的 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;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
// 将兄弟节点的返回指针设置为正在进行的 fiber。
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
// No more siblings. Traverse up.
// 没有更多的兄弟节点。向上遍历。
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}

2. 传播父级上下文更改

备注
function propagateParentContextChanges(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
forcePropagateEntireTree: boolean,
) {
// Collect all the parent providers that changed. Since this is usually small
// number, we use an Array instead of Set.
//
// 收集所有发生变化的父级提供者。由于通常数量较少,
// 我们使用数组而不是 Set。
let contexts = null;
let parent: null | Fiber = workInProgress;
let isInsidePropagationBailout = false;
while (parent !== null) {
if (!isInsidePropagationBailout) {
if ((parent.flags & NeedsPropagation) !== NoFlags) {
isInsidePropagationBailout = true;
} else if ((parent.flags & DidPropagateContext) !== NoFlags) {
break;
}
}

if (parent.tag === ContextProvider) {
const currentParent = parent.alternate;

if (currentParent === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}

const oldProps = currentParent.memoizedProps;
if (oldProps !== null) {
const context: ReactContext<any> = parent.type;
const newProps = parent.pendingProps;
const newValue = newProps.value;

const oldValue = oldProps.value;

if (!is(newValue, oldValue)) {
if (contexts !== null) {
contexts.push(context);
} else {
contexts = [context];
}
}
}
} else if (parent === getHostTransitionProvider()) {
// During a host transition, a host component can act like a context
// provider. E.g. in React DOM, this would be a <form />.
//
// 在宿主环境转换期间,宿主环境组件可以像上下文提供者一样工作
// 例如,在 React DOM 中,这将是一个 <form />。
const currentParent = parent.alternate;
if (currentParent === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}

const oldStateHook: Hook = currentParent.memoizedState;
const oldState: TransitionStatus = oldStateHook.memoizedState;

const newStateHook: Hook = parent.memoizedState;
const newState: TransitionStatus = newStateHook.memoizedState;

// This uses regular equality instead of Object.is because we assume that
// host transition state doesn't include NaN as a valid type.
//
// 这里使用常规的相等比较而不是 Object.is,因为我们
// 假设宿主的过渡状态不把 NaN 作为有效类型。
if (oldState !== newState) {
if (contexts !== null) {
contexts.push(HostTransitionContext);
} else {
contexts = [HostTransitionContext];
}
}
}
parent = parent.return;
}

if (contexts !== null) {
// If there were any changed providers, search through the children and
// propagate their changes.
//
// 如果有任何已更改的提供者,遍历子项并传播它们的更改。
propagateContextChanges(
workInProgress,
contexts,
renderLanes,
forcePropagateEntireTree,
);
}
}

3. 为消费者读取上下文

function readContextForConsumer<T>(
consumer: Fiber | null,
context: ReactContext<T>,
): T {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;

const contextItem = {
context: context as any as ReactContext<mixed>,
memoizedValue: value,
next: null,
};

if (lastContextDependency === null) {
if (consumer === null) {
throw new Error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}

// This is the first dependency for this component. Create a new list.
// 这是该组件的第一个依赖项。创建一个新列表。
lastContextDependency = contextItem;
consumer.dependencies = __DEV__
? {
lanes: NoLanes,
firstContext: contextItem,
_debugThenableState: null,
}
: {
lanes: NoLanes,
firstContext: contextItem,
};
consumer.flags |= NeedsPropagation;
} else {
// Append a new context item.
// 添加一个新的上下文项。
lastContextDependency = lastContextDependency.next = contextItem;
}
return value;
}