Skip to content

8.2 Provider 的实现原理

Context.Provider 的核心职责是接收一个 value 属性,并将其提供给其后代中的消费者。这个过程在 React 的 beginWork 阶段(即Render阶段的“递”过程)中完成,涉及到对 ContextProvider 类型 Fiber 节点的特殊处理。

beginWork 中的 ContextProvider

当 React 在 beginWork 阶段遇到一个 ContextProvider 类型的 Fiber 节点时,它会执行 updateContextProvider 函数。这个函数是处理 Provider 逻辑的入口。

源码路径: packages/react-reconciler/src/ReactFiberBeginWork.js

javascript
function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  const context: ReactContext<any> = workInProgress.type;
  const newProps = workInProgress.pendingProps;
  const newValue = newProps.value;

  // ... DEV a warning if value prop is missing ...

  pushProvider(workInProgress, context, newValue);

  const newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}

updateContextProvider 的逻辑非常清晰:

  1. 获取 Context 对象和新值: 从 workInProgress Fiber 中获取 context 对象 (workInProgress.type) 和新的 value (workInProgress.pendingProps.value)。
  2. 调用 pushProvider: 将新的 value 推入 Context 栈中。这是 Provider 实现的核心步骤。
  3. 继续子节点的 Reconciliation: 调用 reconcileChildren,正常处理 Provider 的子节点。

pushProvider:将 Context 值压入栈

pushProvider 函数负责维护一个贯穿整个渲染过程的 Context 值堆栈。这确保了树中任何位置的组件都能读取到正确的、离它最近的 Provider 的值。

源码路径: packages/react-reconciler/src/ReactFiberNewContext.js

javascript
const valueCursor: StackCursor<mixed> = createCursor(null);

export function pushProvider<T>(
  providerFiber: Fiber,
  context: ReactContext<T>,
  nextValue: T,
): void {
  if (isPrimaryRenderer) {
    push(valueCursor, context._currentValue, providerFiber);
    context._currentValue = nextValue;
  } else {
    // ... for secondary renderer
  }
}

这里的实现非常精妙:

  1. valueCursor: 这是一个栈游标,用于管理所有 Context 的值。它是一个全局的、贯穿整个 reconciler 的栈结构。
  2. 保存旧值: 在更新 context._currentValue 之前,push(valueCursor, context._currentValue, providerFiber) 会将当前context._currentValue 压入 valueCursor 栈中。这就像是为当前的 Context 值创建了一个快照。
  3. 更新当前值: context._currentValue = nextValueContext 对象的当前值更新为 Provider 传入的新 value

popProvider:渲染完成时恢复旧值

当一个 ContextProvider 的所有子节点都完成了 beginWorkcompleteWork(即Render阶段的“归”过程),在 completeWork 的最后阶段,会调用 popProvider 来恢复之前保存的 Context 值。

源码路径: packages/react-reconciler/src/ReactFiberNewContext.js

javascript
export function popProvider(
  context: ReactContext<any>,
  providerFiber: Fiber,
): void {
  const currentValue = valueCursor.current;
  pop(valueCursor, providerFiber);
  if (isPrimaryRenderer) {
    context._currentValue = currentValue;
  } else {
    // ... for secondary renderer
  }
}

popProvider 的操作与 pushProvider 正好相反:

  1. pop(valueCursor, providerFiber): 从 valueCursor 栈中弹出之前保存的值。
  2. 恢复旧值: 将弹出的值(即这个 Provider 渲染之前的 Context 值)恢复到 context._currentValue 上。

总结

Provider 的实现巧妙地利用了执行栈的思想来管理 Context 的值。在 beginWork 向下遍历组件树时,pushProvider 将旧值入栈并设置新值;在 completeWork 向上回溯时,popProvider 将旧值出栈并恢复。这保证了只有在 Provider 组件的“作用域”(即其子树)内,Context 的值才是 Provider 所提供的 value,一旦渲染离开这个子树,Context 的值就会恢复到之前的值。

这个机制是 React 中许多“环境”相关特性(如事件系统、Suspense)实现的基础,它高效、准确地保证了在组件树的不同部分能读到正确的环境值。

Last updated: