react原理 - context的实现

134 阅读3分钟

Context

context 对象

  • 通过 createContext创建
  • 本质: REACT_CONTEXT_TYPE 类型的react元素
  1. _currentValue: 用来传递值(value)
  
  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    _currentValue: defaultValue,
    _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),
  };

readContext

  • 建立当前 fiber.dependencies 和 context对象的关联
  1. fiber.dependencies 已链表的形式存储着 createItem .
	// readContext
	// 创建 contextItem 对象 
    const contextItem = {
      context: ((context: any): ReactContext<mixed>),
      observedBits: resolvedObservedBits,
      next: null,
    };
    if (lastContextDependency === null) {
      // This is the first dependency for this component. Create a new list.
      lastContextDependency = contextItem;
      currentlyRenderingFiber.dependencies = {
        lanes: NoLanes,
        firstContext: contextItem,
        responders: null,
      };
    } else {
      // Append a new context item.
      lastContextDependency = lastContextDependency.next = contextItem;
    }
    // 返回 _currentValue
    return isPrimaryRenderer ? context._currentValue : context._currentValue2;
	```

#### Provider
- 本质: `REACT_PROVIDER_TYPE类型的react元素`
1. `_context`: 保存则对当前 `context对象的引用`

```js
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };
updateContextProvider : 调和 Provider
  • pushProvider : 重新设置 context._currentValue为 <Provider value/> 这也是 Provider传递值的方式即通过 currentValue传递
function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  const providerType: ReactProviderType<any> = workInProgress.type;
  // context原理: 获去 context对象
  const context: ReactContext<any> = providerType._context;
  const newProps = workInProgress.pendingProps;
  const oldProps = workInProgress.memoizedProps;
  const newValue = newProps.value;
  // context原理: 设置 context._currentValue 为 Provider的value属性
  pushProvider(workInProgress, newValue);

  if (oldProps !== null) {
    const oldValue = oldProps.value;
    // context原理: 对比 newValue 和 oldValue看是否发生了更新,如果没有则返回 0
    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,
          renderLanes,
        );
      }
    } else {
      // The context value changed. Search for matching consumers and schedule
      // them to update.
      // context原理: 更新阶段
      propagateContextChange(workInProgress, context, changedBits, renderLanes);
    }
  }
  // context原理: 开始调和子fiber
  const newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}
  • calculateChangedBits: 对比 newValue 和 oldValue看是否发生了更新,如果没有则返回 0.
  • bailoutOnAlreadyFinishedWork: 没有更新则命中 bailoutOnAlreadyFinishedWork 直接 cloneChild继续调和子树.
  • propagateContextChange: 否则进入更新逻辑
propagateContextChange: 提高所有使用了当前context的fiber的lanes
  • 遍历子fiber , 通过 fiber.dependencies 找到所有使用context的子fiber, 并将其父链路上的所有fiber的lanes优先级提高(scheduleWorkOnParentPath).在后续调和过程中确保其继续调和.
  1. 这里就是 context 说性能不高的原因吗?因为在寻找使用了 context对象的 fiber时,需要遍历整棵子树
export function propagateContextChange(
  workInProgress: Fiber,
  context: ReactContext<mixed>,
  changedBits: number,
  renderLanes: Lanes,
): void {
  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.
    const list = fiber.dependencies;
    console.log('list', list);
    if (list !== null) {
      nextFiber = fiber.child;
      let dependency = list.firstContext;
      while (dependency !== null) {
        // Check if the context matches.
        if (
          dependency.context === context &&
          (dependency.observedBits & changedBits) !== 0
        ) {
          // Match! Schedule an update on this fiber.
          // context原理: 如果类组件使用context时,则创建一个高优先级的ForceUpdate update对象
          if (fiber.tag === ClassComponent) {
            // Schedule a force update on the work-in-progress.
            const update = createUpdate(
              NoTimestamp,
              pickArbitraryLane(renderLanes),
            );
            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);
          }
          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
          const alternate = fiber.alternate;
          if (alternate !== null) {
            alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
          }
          // context原理: 给使用 context的fiber的所有父标记为更新
          scheduleWorkOnParentPath(fiber.return, renderLanes);
          // 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.
          break;
        }
        // context原理: 一个fiber可能对应多个context对象
        dependency = dependency.next;
      }
    } 
    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) {
        // context原理: 如果是 Provider对应的fiber节点则返回
        if (nextFiber === workInProgress) {
          // We're back to the root of this subtree. Exit.
          nextFiber = null;
          break;
        }
		// context原理: 继续处理 siblings
        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;

Consumer

  • 本质: 就是 context 对象
context.Consumer = context;
useContext
  • 本质就是 [[#readContext]] 方法
  1. contextItem.context 就是 useContext 传递过来的context对象
Consumer 的 render props
  • 核心实现也是 [[#readContext]]
  1. contextItem.context 是通过 fiber.type 进行获取的,Consumer的type 就是 [[#context 对象]]
  • 具体实现见 updateContextConsumer
function updateContextConsumer(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  // context原理: 获取 contex对象
  let context: ReactContext<any> = workInProgress.type;
  const newProps = workInProgress.pendingProps;
  // context原理: 获取 renderProps
  const render = newProps.children;
  prepareToReadContext(workInProgress, renderLanes);
  // context原理: 创建createItem
  const newValue = readContext(context, newProps.unstable_observedBits);
  let newChildren;
  // context原理: 调用 render Props 继续调和子fiber
  newChildren = render(newValue);
  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}
contextType
  • 本质也是 [[#readContext]]
  1. 在[[#updateContextProvider]] 中 , 如果类组件使用context时,则创建一个高优先级的ForceUpdate update对象