lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

context #13

Open lz-lee opened 4 years ago

lz-lee commented 4 years ago

context

stack 概念

// ReactFiberStack.js

// 所有值存储的数组
const valueStack: Array<any> = [];
let index = -1;

function createCursor<T>(defaultValue: T): StackCursor<T> {
  return {
    current: defaultValue,
  };
}

function isEmpty(): boolean {
  return index === -1;
}

// 入栈
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
  // index 累加
  index++;

  valueStack[index] = cursor.current;

  // 新的 value 不会推入栈
  cursor.current = value;
}

// 出栈
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
  if (index < 0) {
    return;
  }

  // 老的值赋值出来
  cursor.current = valueStack[index];
  // 清空 index 位置的值
  valueStack[index] = null;
  // 累减
  index--;
}
// ReactFiberContext.js
// 老 context 的两种 cursor
const emptyContextObject = {};
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);

// ReactFiberNewContext.js
// 新 context cursor
const valueCursor: StackCursor<mixed> = createCursor(null);

LegacyContext 老版本的 context

父组件提供 getChildContext 方法、声明 childContextTypes 属性,子组件声明 contextTypes 属性的方式获取 context 的问题

updateHostRoot 对 FiberRoot 会执行第一次 context push,

function updateHostRoot(current, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  // ...
}

function pushHostRootContext(workInProgress) {
  const root = (workInProgress.stateNode: FiberRoot);
  // 初次渲染如果有 pendingContext
  if (root.pendingContext) {
    pushTopLevelContextObject(
      workInProgress,
      root.pendingContext,
      root.pendingContext !== root.context,
    );
  } else if (root.context) {
    // Should always be set
    // 除了初次渲染 push 的值是 false,表明目前 context 没有变化
    pushTopLevelContextObject(workInProgress, root.context, false);
  }
  pushHostContainer(workInProgress, root.containerInfo);
}

function pushTopLevelContextObject(
  fiber: Fiber,
  context: Object,
  didChange: boolean,
): void {
  push(contextStackCursor, context, fiber);
  push(didPerformWorkStackCursor, didChange, fiber);
}

之后只有 classComponent 能提供 childContext, 在 updateClassComponent 中 push 子树的 context 对象,

// updateClassComponent

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  // 判断是否是老 context 方式提供 context ,如果是,则 push,并将hasContext 置为 true ,在 finishClassComponent 中会用到
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  // 新的 context 相关
  prepareToReadContext(workInProgress, renderExpirationTime);

  // ...
}

function isContextProvider(type: Function): boolean {
  const childContextTypes = type.childContextTypes;
  return childContextTypes !== null && childContextTypes !== undefined;
}

pushContextProvider

invalidateContextProvider

function finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderExpirationTime: ExpirationTime, ) { // Refs should update even if shouldComponentUpdate returns false markRef(current, workInProgress);

const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;

// 不需要更新且没有错误则跳过更新 if (!shouldUpdate && !didCaptureError) { // Context providers should defer to sCU for rendering // updateClassComponent 中使用了老 context 后会赋值为 true // didChange 为 false if (hasContext) { invalidateContextProvider(workInProgress, Component, false); } // 跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } // ... if (hasContext) { invalidateContextProvider(workInProgress, Component, true); }

return workInProgress.child; }

// invalidateContextProvider

function invalidateContextProvider( workInProgress: Fiber, type: any, didChange: boolean, // 组件是否有更新 ): void { const instance = workInProgress.stateNode;

// 组件需要更新 if (didChange) { // Merge parent and own context. // Skip this if we're not updating due to sCU. // This avoids unnecessarily recomputing memoized values. // 重新计算新的合并过的 context,(会合并父组件的 context ) const mergedContext = processChildContext( workInProgress, type, previousContext, // 上一次 push 的 context 的值 ); // 挂载到 reactInternalMemoizedMergedChildContext 属性上 instance.reactInternalMemoizedMergedChildContext = mergedContext;

// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
// finishClassComponent 之前在 updateClassComponent 的时候 push 了一次老的,如果有更新,需要将老的 pop
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
// 再 push 新计算出来的 context
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);

} else { // 没有更新,有没有更新都需要将 didPerformWorkStackCursor.current 同步 didChange, 如果不同步 false 的情况那组件 beginWork 时会一直更新,没有可以跳过更新的情况 // pop 老的 pop(didPerformWorkStackCursor, workInProgress); // push 新的 // 仍需要 push didChange 将 didPerformWorkStackCursor.current = false push(didPerformWorkStackCursor, didChange, workInProgress); } }


####  processChildContext
- 合并父组件的 `context` ,相同 `key` 属性会被覆盖
```js
function processChildContext(
  fiber: Fiber,
  type: any,
  parentContext: Object, // 全局变量 previousContext
): Object {
  const instance = fiber.stateNode;
  const childContextTypes = type.childContextTypes;

  // TODO (bvaughn) Replace this behavior with an invariant() in the future.
  // It has only been added in Fiber to match the (unintentional) behavior in Stack.
  // 如果没有 getChildContext 方法就直接返回父组件的 context
  if (typeof instance.getChildContext !== 'function') {

    return parentContext;
  }

  let childContext;
  // 获取新的 context
  childContext = instance.getChildContext();

  // 合并 context
  return {...parentContext, ...childContext};
}

hasContextChanged

// beginWork function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { const updateExpirationTime = workInProgress.expirationTime;

// 非首次渲染 if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps === newProps && // 新老 props 相等 !hasLegacyContextChanged() && // 父组件的 context 没有变化 (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) ) { // This fiber does not have any pending work. Bailout without entering // the begin phase. There's still some bookkeeping we that needs to be done // in this optimized path, mostly pushing stuff onto the stack. switch (workInProgress.tag) { case HostRoot: pushHostRootContext(workInProgress); resetHydrationState(); break; case HostComponent: pushHostContext(workInProgress); break; case ClassComponent: { const Component = workInProgress.type; // 有 childContextTypes 属性 if (isLegacyContextProvider(Component)) { // 入栈 pushLegacyContextProvider(workInProgress); } break; } case HostPortal: pushHostContainer( workInProgress, workInProgress.stateNode.containerInfo, ); break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; pushProvider(workInProgress, newValue); break; } case Profiler: if (enableProfilerTimer) { workInProgress.effectTag |= Update; } break; case SuspenseComponent: { // ... break; } } // 跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } // .... }


### completeWork 中 pop context
- `popContext`: `pop` 的顺序和 `push` 时相反,最后 `push` 的 `didPerformWorkStackCursor` 先 `pop`,确保从 `valueStack` 中取值正确
```js
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      break;
    case LazyComponent:
      break;
    case SimpleMemoComponent:
    case FunctionComponent:
      break;
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    // ....
  }
  // ...
}

// popLegacyContext => popContext

function popContext(fiber: Fiber): void {
  pop(didPerformWorkStackCursor, fiber);
  pop(contextStackCursor, fiber);
}

子组件从父组件提供的 context 对象里获取需要的属性

// updateFunctionComponent
function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {

  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  // ...
  return workInProgress.child;
}

// updateClassInstance
function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props = oldProps;

  const oldContext = instance.context;
  // 新的 context api 使用方式
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    nextContext = readContext(contextType);
  } else {
    // 老的 context
    // 组件自己声明的 contextTypes 是给组件的子节点用的,自己要使用 context 来更新,应该用 parent 提供的 context,即 previousContext
    // 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    // 子组件从父组件合并过后的 context 中选择性属性
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }
  // ...
}

getUnmaskedContext

-

function getUnmaskedContext(
  workInProgress: Fiber,
  Component: Function,
  didPushOwnContextIfProvider: boolean, // 读 context 一般都为 true
): Object {
  // 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
  if (didPushOwnContextIfProvider && isContextProvider(Component)) {
    // If the fiber is a context provider itself, when we read its context
    // we may have already pushed its own child context on the stack. A context
    // provider should not "see" its own child context. Therefore we read the
    // previous (parent) context instead for a context provider.
    return previousContext;
  }
  // 不是 provider 则读当前的 cursor 就可以
  // 或者 mountIndeterminateComponent 函数里 didPushOwnContextIfProvider 为 false
  return contextStackCursor.current;
}

getMaskedContext

function getMaskedContext(
  workInProgress: Fiber,
  unmaskedContext: Object,
): Object {
  const type = workInProgress.type;

  const contextTypes = type.contextTypes;
  if (!contextTypes) {
    return emptyContextObject;
  }

  // Avoid recreating masked context unless unmasked context has changed.
  // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
  // This may trigger infinite loops if componentWillReceiveProps calls setState.
  const instance = workInProgress.stateNode;
  // 如果有缓存则从缓存里读
  if (
    instance &&
    instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
  ) {
    return instance.__reactInternalMemoizedMaskedChildContext;
  }

  const context = {};
  // 根据 contextTypes 上声明的 key 读取父组件合并过后的 context 中的值
  for (let key in contextTypes) {
    context[key] = unmaskedContext[key];
  }

  // Cache unmasked context so we can avoid recreating masked context unless necessary.
  // Context is created before the class component is instantiated so check for instance.
  // 缓存 context
  if (instance) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return context;
}

// 缓存
function cacheContext(
  workInProgress: Fiber,
  unmaskedContext: Object,
  maskedContext: Object,
): void {
  const instance = workInProgress.stateNode;
  instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
  instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}

新 context

ReactContext

case ContextProvider: return updateContextProvider( current, workInProgress, renderExpirationTime, );

function updateContextProvider( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { const providerType: ReactProviderType = workInProgress.type; // _context 指向创建的 context 对象(也是 Consumer) const context: ReactContext = providerType._context;

const newProps = workInProgress.pendingProps; const oldProps = workInProgress.memoizedProps;

const newValue = newProps.value; // push 新的 value pushProvider(workInProgress, newValue);

if (oldProps !== null) { const oldValue = oldProps.value; // 模拟 Object.is // 如果 newValue, oldValue 相等返回 0 // 如果不相等则返回 MAX_SIGNED_31_BIT_INT const changedBits = calculateChangedBits(context, newValue, oldValue); if (changedBits === 0) { // No change. Bailout early if children are the same. if ( oldProps.children === newProps.children && !hasLegacyContextChanged() ) { // newValue, oldValue相等 且新老 children 相同,且没有老 context变化的条件下,可以跳过更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } else { // The context value changed. Search for matching consumers and schedule // them to update. // 有更新,则遍历子节点对有 firstContextDependency 的 classComponent 类型的节点创建一个 forceUpdate 的更新 propagateContextChange( workInProgress, context, changedBits, renderExpirationTime, ); } }

const newChildren = newProps.children; // 调和子节点 reconcileChildren(current, workInProgress, newChildren, renderExpirationTime); return workInProgress.child; }


#### pushProvider

```js
function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
  // context 对象
  const context: ReactContext<T> = providerFiber.type._context;

  // ReactDOM 下 isPrimaryRenderer 为 true
  if (isPrimaryRenderer) {
    // push valueCursor 值为 context._currentValue
    // valueCursor 记录的是当前这棵树下面一共经历了几个 provider 对应的值
    push(valueCursor, context._currentValue, providerFiber);
  // consumer 读取值是从 context._currentValue 读取的,与 v alueCursor 无关
    context._currentValue = nextValue;
  } else {
  }
}

propagateContextChange

updateContextConsumer

function updateContextConsumer( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { let context: ReactContext = workInProgress.type; const newProps = workInProgress.pendingProps; // consumer 的 children 为一个函数,这个函数的参数即 provider 提供的 value const render = newProps.children;

// 初始化 currentlyRenderingFiber 、workInProgress.firstContextDependency prepareToReadContext(workInProgress, renderExpirationTime); // 读取 context._currentValue , const newValue = readContext(context, newProps.unstable_observedBits); let newChildren; if (DEV) { // ... } else { // 调用 render 获取children newChildren = render(newValue); }

// React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; // 调和子节点 reconcileChildren(current, workInProgress, newChildren, renderExpirationTime); return workInProgress.child; }


#### prepareToReadContext
- 往当前 `fiber` 上添加 `firstContextDependency` 属性,值为 `null`
- 设置全局变量 `currentlyRenderingFiber` 为当前 `fiber` 节点
- 设置全局变量 `lastContextDependency` 值为 `null`,用于构建单链表结构

```js
function prepareToReadContext(
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): void {
  currentlyRenderingFiber = workInProgress;
  lastContextDependency = null;
  lastContextWithAllBitsObserved = null;

  // Reset the work-in-progress list
  workInProgress.firstContextDependency = null;
}

readContext

function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean, // => undefined
): T {
  // 首次进入这个函数 lastContextWithAllBitsObserved 为 null
  if (lastContextWithAllBitsObserved === context) {
    // Nothing to do. We already observe everything in this context.
    // unstable_observedBits 目前版本下为 undefined
  } else if (observedBits === false || observedBits === 0) {
    // Do not observe any updates.
  } else {
    let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
    if (
      typeof observedBits !== 'number' ||
      observedBits === MAX_SIGNED_31_BIT_INT
    ) {
      // Observe all updates.
      // 赋值 lastContextWithAllBitsObserved 为当前 context
      lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
      // 赋值 resolvedObservedBits ,
      resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
    } else {
      resolvedObservedBits = observedBits;
    }

    let contextItem = {
      context: ((context: any): ReactContext<mixed>),
      // 这个值即在 updateContextProvider 中的 propagateContextChange 函数中判断 (dependency.observedBits & changedBits) !== 0
      observedBits: resolvedObservedBits,
      next: null,
    };

    if (lastContextDependency === null) {
      // This is the first dependency in the list
      // 构建 firstContextDependency 到 lastContextDependency 的 单链表结构
      currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
    } else {
      // Append a new context item.
      lastContextDependency = lastContextDependency.next = contextItem;
    }
  }
  // ReactDOM 下 isPrimaryRenderer 为 true,直接返回 context._currentValue
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
// prepareToReadContext

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  //....
}

function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps,
  renderExpirationTime: ExpirationTime,
) {
  // Push context providers early to prevent context stack mismatches.
  // During mounting we don't know the child context yet as the instance doesn't exist.
  // We will invalidate the child context in finishClassComponent() right after rendering.
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  // 新的 context 相关, 设置 workInProgress.firstContextDependency 为null
  prepareToReadContext(workInProgress, renderExpirationTime);

  // ....
}

// readContext
function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  let isLegacyContextConsumer = false;
  let unmaskedContext = emptyContextObject;
  let context = null;
  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    // 新 context 获取方式
    context = readContext((contextType: any));
  } else {
    unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    const contextTypes = ctor.contextTypes;
    isLegacyContextConsumer =
      contextTypes !== null && contextTypes !== undefined;
    context = isLegacyContextConsumer
      ? getMaskedContext(workInProgress, unmaskedContext)
      : emptyContextObject;
  }
    // 往 instance 上挂载 context
    const instance = new ctor(props, context);
    // ....
}

function mountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): void {
  if (__DEV__) {
    checkClassInstance(workInProgress, ctor, newProps);
  }

  const instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;

  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    // 挂载 context
    instance.context = readContext(contextType);
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }
}

function resumeMountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props = oldProps;

  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    // 挂载 context
    nextContext = readContext(contextType);
  } else {
    const nextLegacyUnmaskedContext = getUnmaskedContext(
      workInProgress,
      ctor,
      true,
    );
    nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
  }
}