Context 原理
Context 原理
Provider传递流程:Provider的更新,会深度遍历子代Fiber,消费context的Fiber和父级链都会提升更新优先级。对于类组件的Fiber,会forceUpdate处理。接下来所有消费的Fiber,都会beginWork。context订阅流程:contextType、useContext、Consumer会内部调用readContext。readContext会把Fiber上的dependencies属性和context对象建立起关联。
Context 对象
React.createContext(defaultValue) 创建一个 Context 对象。接受一个 defaultValue 参数,作为默认值。
Context 对象通过调用 createContext 函数进行创建。
createContext(defaultValue) 函数
// react\packages\react\src\ReactContext.js
import { REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE } from 'shared/ReactSymbols'
// export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
// export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
import type { ReactContext } from 'shared/ReactTypes'
export function createContext<T>(defaultValue: T): ReactContext<T> {
  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue, // 用来保存传递给 Provider 的 value
    _currentValue2: defaultValue,
    // Used to track how many concurrent renderers this context currently
    // supports within in a single renderer. Such as parallel server rendering.
    _threadCount: 0,
    // These are circular
    Provider: (null: any),
    Consumer: (null: any),
    // Add these to use same hidden class in VM as ServerContext
    _defaultValue: (null: any),
    _globalName: (null: any),
  }
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  }
  let hasWarnedAboutUsingNestedContextConsumers = false
  let hasWarnedAboutUsingConsumerProvider = false
  let hasWarnedAboutDisplayNameOnConsumer = false
  context.Consumer = context
  return context
}Provider 提供者
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
<MyContext.Provider value={/* 某个值 */}>Context.Provider 本质上是一个类型为 REACT_PROVIDER_TYPE 特殊的 React Element 对象,转化为 Fiber 的类型为 ContextProvider。
对于 ContextProvider 类型的 Fiber,在 Reconciler render 阶段(调和阶段)的 beginWork 流程中会调用 updateContextProvider(current, workInProgress, renderLanes) 函数进行处理。
在 updateContextProvider(current, workInProgress, renderLanes) 函数中:
- 调用 
pushProvider(workInProgress, context, newValue)将Provider的value属性,赋值给context对象(即:workInProgress.type属性上的_context)的_currentValue属性上 - 如果 
Context的value值没有改变,则会调用bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)函数复用节点 - 如果 
Context的value值发生改变,则会调用propagateContextChange(workInProgress, context, renderLanes)函数更新节点 
updateContextProvider(current, workInProgress, renderLanes) 函数
function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
) {
  const providerType: ReactProviderType<any> = workInProgress.type
  const context: ReactContext<any> = providerType._context
  const newProps = workInProgress.pendingProps
  const oldProps = workInProgress.memoizedProps
  const newValue = newProps.value
  // 获取 Provider 上的 value 值
  pushProvider(workInProgress, context, newValue)
  if (enableLazyContextPropagation) {
    // In the lazy propagation implementation, we don't scan for matching
    // consumers until something bails out, because until something bails out
    // we're going to visit those nodes, anyway. The trade-off is that it shifts
    // responsibility to the consumer to track whether something has changed.
  } else {
    // 更新 Context
    if (oldProps !== null) {
      const oldValue = oldProps.value
      if (is(oldValue, newValue)) {
        // No change. Bailout early if children are the same.
        // context value 没有改变,如果 children 是一样的,则不需要更新
        if (
          oldProps.children === newProps.children &&
          !hasLegacyContextChanged()
        ) {
          return bailoutOnAlreadyFinishedWork(
            current,
            workInProgress,
            renderLanes
          )
        }
      } else {
        // The context value changed. Search for matching consumers and schedule
        // them to update.
        // context value 改变,搜索匹配 consumers 并进行调度更新
        propagateContextChange(workInProgress, context, renderLanes)
      }
    }
  }
  const newChildren = newProps.children
  reconcileChildren(current, workInProgress, newChildren, renderLanes)
  return workInProgress.child
}在 propagateContextChange(workInProgress, context, renderLanes) 函数中
- 对于 
CacheComponent,调用propagateContextChanges来寻找匹配的consumers - 对于其它类型的组件,则调用 
propagateContextChange_eager来寻找匹配的consumers 
propagateContextChanges 和 propagateContextChange_eager 的功能差不多
深度遍历所有的子代
Fiber,获取Fiber节点的dependencies的属性dependencies属性可以把当前的Fiber节点和context建立起关联,即使用了当前context的Fiber节点 会把context放在dependencies中。dependencies属性本身是一个链表结构,一个Fiber节点可以有多个context与之对应对比
dependencies中的context和当前Provider的context是否是同一个。如果是同一个,并且当前Fiber是类组件ClassComponent,则绑定一个forceUpdate标识。提高Fiber的更新优先级,让Fiber在接下来的调和过程中,处于一个高优先级待更新的状态。将当前
Fiber节点的update优先级标记为高优先级,并修改当前Fiber节点父路径上所有节点的childLanes属性。
propagateContextChange(workInProgress, context, renderLanes) 函数
export function propagateContextChange<T>(
  workInProgress: Fiber,
  context: ReactContext<T>,
  renderLanes: Lanes
): void {
  if (enableLazyContextPropagation) {
    // 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
    )
  } else {
    propagateContextChange_eager(workInProgress, context, renderLanes)
  }
}propagateContextChanges(workInProgress, [context], renderLanes, forcePropagateEntireTree) 函数
function propagateContextChange_eager<T>(
  workInProgress: Fiber,
  context: ReactContext<T>,
  renderLanes: Lanes
): void {
  // Only used by eager implementation
  if (enableLazyContextPropagation) {
    return
  }
  let fiber = workInProgress.child
  if (fiber !== null) {
    // Set the return pointer of the child to the work-in-progress fiber.
    // 将 fiber 的 return 属性指向当前工作的 workInProgress
    // fiber 节点的 return 属性指向父节点
    fiber.return = workInProgress
  }
  while (fiber !== null) {
    let nextFiber
    // Visit this fiber.
    // 在 readContext() 中创建了 context 的依赖列表,并将依赖列表添加到了 fiber 节点上
    // 这里从 fiber 节点上取出 context 的依赖列表,对依赖列表进行检查
    const list = fiber.dependencies
    if (list !== null) {
      nextFiber = fiber.child
      let dependency = list.firstContext
      while (dependency !== null) {
        // Check if the context matches.
        // 查找匹配的 consumers
        if (dependency.context === context) {
          // Match! Schedule an update on this fiber.
          // 查找到匹配的 context,则安排调度
          if (fiber.tag === ClassComponent) {
            // Schedule a force update on the work-in-progress.
            // 设置 update 为 高优先级
            const lane = pickArbitraryLane(renderLanes)
            // 将当前 fiber 设置为 ForceUpdate,保证 class 组件一定执行 render
            const update = createUpdate(NoTimestamp, lane)
            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.
            // Inlined `enqueueUpdate` to remove interleaved update check
            const updateQueue = fiber.updateQueue
            if (updateQueue === null) {
              // Only occurs if the fiber has been unmounted.
            } else {
              const sharedQueue: SharedQueue<any> = (updateQueue: any).shared
              const pending = sharedQueue.pending
              if (pending === null) {
                // This is the first update. Create a circular list.
                update.next = update
              } else {
                update.next = pending.next
                pending.next = update
              }
              sharedQueue.pending = update
            }
          }
          // 标记优先级
          fiber.lanes = mergeLanes(fiber.lanes, renderLanes)
          const alternate = fiber.alternate
          if (alternate !== null) {
            alternate.lanes = mergeLanes(alternate.lanes, renderLanes)
          }
          // 修改当前fiber节点父路径上所有节点的 childLanes 属性
          scheduleContextWorkOnParentPath(
            fiber.return,
            renderLanes,
            workInProgress
          )
          // Mark the updated lanes on the list, too.
          // 标记优先级
          list.lanes = mergeLanes(list.lanes, renderLanes)
          // Since we already found a match, we can stop traversing the
          // dependency list.
          // 已经找到了匹配的 context,退出遍历依赖列表
          break
        }
        dependency = dependency.next
      }
    } else if (fiber.tag === ContextProvider) {
      // Don't scan deeper if this is a matching provider
      nextFiber = fiber.type === workInProgress.type ? null : fiber.child
    } 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.
      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.
      scheduleContextWorkOnParentPath(
        parentSuspense,
        renderLanes,
        workInProgress
      )
      nextFiber = fiber.sibling
    } else {
      // Traverse down.
      nextFiber = fiber.child
    }
    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
        }
        const 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
  }
}消费者
订阅者 Context.Consumer 方式
const MyContext = React.createContext(null)
export default function () {
  return (
    <MyContext.Consumer>
      {/* 将 contextValue 内容转化成 props  */}
      {contextValue => <ConsumerComponent {...contextValue} />}
    </MyContext.Consumer>
  )
}Context.Consumer 方式采取 render props 函数方式。
- 接收组件树中匹配最近的 
<MyContext.Provider>提供的 Contextvalue值,作为 render props 函数的参数。 - 将接受的参数取出,作为函数返回的 React 节点的 
props传入。 
Context.Consumer 本质上是一个类型为 REACT_CONTEXT_TYPE 的 React Element 对象。转化为 Fiber 的类型为 ContextConsumer。
对于 ContextConsumer 类型的 Fiber,在 Reconciler render 阶段(调和阶段)的 beginWork 流程中会调用 updateContextConsumer(current, workInProgress, renderLanes) 函数进行处理。
在 updateContextConsumer(current, workInProgress, renderLanes) 函数中:
调用
readContext(context)函数获取最新的value在
readContext(context)函数中:创建一个
contextItemFiber节点上会存在多个dependencies以链表的形式联系到一起如果不存在最后一个
context dependency,则context dependencies为空,创建第一个dependency如果存在最后一个
dependency,则contextItem会以链表形式保存,并变成最后一个lastContextDependency
readContext(context) 函数
export function readContext<T>(context: ReactContext<T>): T { const value = isPrimaryRenderer ? context._currentValue : context._currentValue2 if (lastFullyObservedContext === context) { // Nothing to do. We already observe everything in this context. } else { const contextItem = { context: ((context: any): ReactContext<mixed>), memoizedValue: value, next: null, } if (lastContextDependency === null) { if (currentlyRenderingFiber === 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 currentlyRenderingFiber.dependencies = { lanes: NoLanes, firstContext: contextItem, } if (enableLazyContextPropagation) { currentlyRenderingFiber.flags |= NeedsPropagation } } else { // Append a new context item. lastContextDependency = lastContextDependency.next = contextItem } } return value }通过
render(newValue)函数,传入最新的value,得到最新的newChildren调和
newChildren
updateContextConsumer(current, workInProgress, renderLanes) 函数
function updateContextConsumer(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
) {
  let context: ReactContext<any> = workInProgress.type
  const newProps = workInProgress.pendingProps
  const render = newProps.children
  // 读取 context
  prepareToReadContext(workInProgress, renderLanes)
  // 得到最新的新的 context value
  const newValue = readContext(context)
  if (enableSchedulingProfiler) {
    markComponentRenderStarted(workInProgress)
  }
  let newChildren
  // 得到最新的 children element
  newChildren = render(newValue)
  if (enableSchedulingProfiler) {
    markComponentRenderStopped()
  }
  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork
  // 调和 children
  reconcileChildren(current, workInProgress, newChildren, renderLanes)
  return workInProgress.child
}函数组件 useContext 方式
函数组件 useContext 方式本质上调用 readContext 方法。
函数组件通过 readContext ,将函数组件的 dependencies 和当前 context 建立起关联,context 改变,将当前函数组件设置高优先级,促使其渲染。
类组件 Class.contextType 方式
类组件 Class.contextType 方式 和 useContext 一样,本质上就是调用 readContext 方法。
静态属性 contextType,在类组件实例化的时候被使用,本质上也是调用 readContext 将 context 和 Fiber 上的 dependencies 建立起关联。
