Skip to content

对应源码:packages/react/src/ReactContext.js

1. createContext

javascript
// packages/react/src/ReactContext.js(简化)

export function createContext<T>(defaultValue: T): ReactContext<T> {
  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
  };

  // Provider 就是 context 自身(不是单独的对象)
  context.Provider = context;
  // Consumer 是一个单独的对象
  context.Consumer = {
    $$typeof: REACT_CONSUMER_TYPE,
    _context: context,
  };

  return context;
}

注意:在当前源码中,Provider 直接指向 context 自身(context.Provider = context),而不是像旧版 React 那样创建一个带 REACT_PROVIDER_TYPE 的单独对象。这意味着 <Context.Provider><Context> 在 JSX 中是等效的。

Context 对象本身携带 $$typeof: REACT_CONTEXT_TYPE 标记,Reconciler 通过检查 Fiber 的 type 来区分它是一个 Provider 还是一个 Consumer。

2. 传播机制

Context 的 value 变化流程:

1. <Context.Provider value={newValue}>
   → beginWork 中 updateContextProvider
   → pushProvider(workInProgress, context, newValue)
     → context._currentValue = newValue(push 到 context 栈)

2. Lazy Propagation(不是主动遍历子树!)
   Provider 的 beginWork 只 push value + reconcileChildren
   不主动通知 Consumer

   Consumer 的 beginWork 时(或 bailout 检查时):
   → lazilyPropagateParentContextChanges(current, workInProgress, renderLanes)
   → 向上遍历父节点,找到 ContextProvider 类型
   → 比较 current.memoizedProps.value vs pendingProps.value
   → 如果变了 → 标记消费此 context 的子树 fiber.lanes |= renderLanes

3. 消费的组件重新渲染:
   → useContext(context)
   → readContext(context) → 读取 context._currentValue

4. completeWork 中 popProvider
   → 恢复 context._currentValue 为父 Provider 的值

Context 的值传播通过"栈"实现:Provider push 值,Consumer 读栈顶值,Provider 在 completeWork 时 pop。传播机制本身是 lazy 的——Consumer 在自己的 beginWork 时主动向上查找变更的 Provider,而非 Provider 主动向下通知。

3. 为什么 Context 变化绕过 React.memo

React.memo 只阻止 props 变化的重渲染。Context 变化通过 lazy propagation 机制直接修改消费 Fiber 的 fiber.lanes,不走 props 比较路径,因此 memo 无法拦截。更多细节可参考卡颂的 React 技术揭秘 - Context

下一步

参考资料

Released under the MIT License.