Context API

React 16 提供有四个context相关的API

  • createContext
  • Provider
  • contextType
  • Consumer

例子


const calculateChangedBits = (currentValue, nextValue) =>
currentValue.name !== nextValue.name ? 0b10 : 0b00;

const UserContext = React.createContext(null,calculateChangedBits);

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      name: "beforeUpdate"
    };
  }
  setName = () => {
    this.setState(state => ({
      name: state.name === "beforeUpdate" ? "afterUpdate" : "beforeUpdate"
    }));
  };
  render() {
    return (
        <UserContext.Provider value={{ name: this.state.name ,setName:this.setName}}>
         <Indirection>
          <User />
        </Indirection>
        </UserContext.Provider>
    );
  }
}

function User() {
  return (
    <UserContext.Consumer unstable_observedBits={0b10}>
      {({name,setName}) => (
        <button
        onClick={setName}
          >
          {name}
        </button>
      )}
    </UserContext.Consumer>
  );
}
class Indirection extends React.Component {
  shouldComponentUpdate() {
    return false;
  }
  render() {
    return this.props.children;
  }
}


export default App;

React.createContext()

传入一个初始值/默认值,创建一个 ReactContext,会生成 Provider 与 Consumer,它们是 fiber 中具有特殊类型($$typeof)的节点,同时是内存共享的,这对后续 Consumer 接收 Provider 的值,以及实现跨层级更新都非常重要。

// 创建 React context 赋默认值
const {Consumer,Provider} = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});
export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    //用于细粒度的更新控制
    _calculateChangedBits: calculateChangedBits,
     //这里两个currentValue是为了支持多个renderer的
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    _threadCount: 0,
    // 这里是一个循环引用
    Provider: (null: any),
    Consumer: (null: any),
  };

  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;
  context.Consumer = context;

  return context;
}

Consumer和Provider存在循环引用,为了方便检测这二者是否匹配

ReactContext.Provider

Provider是context的提供者,我们可以给这个组件传一个value值来覆盖createContext传入的默认值,当value值变化时就会通知到子级的消费者。

当 provider 的 value 变化时,会把当前 provider 的 value 赋值给 ReactContext._currentValue,后面我们的 consum 可以直接从_currentValue去获取最新的值

Provider 所对应的 fiber 节点在创建时,调用updateContextProvider方法


function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  const providerType: ReactProviderType<any> = workInProgress.type;
  const context: ReactContext<any> = providerType._context;

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

  const newValue = newProps.value;

    //将新的value赋值currentValue
  pushProvider(workInProgress, newValue);

  if (oldProps !== null) {
    const oldValue = oldProps.value;
    //运行传入的calculateChangedBits方法,判断有无改变
    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,
          renderExpirationTime,
        );
      }
    } else {
        //context 的 value发生了变化就搜索匹配的consumer,找到后安排调度更新
        //修改其expirationTime,并向上遍历,修改祖先的childExpirationTime
      propagateContextChange(
        workInProgress,
        context,
        changedBits,
        renderExpirationTime,
      );
    }
  }

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

export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
  const context: ReactContext<T> = providerFiber.type._context;

  if (isPrimaryRenderer) {
    push(valueCursor, context._currentValue, providerFiber);

    context._currentValue = nextValue;

  } else {
    push(valueCursor, context._currentValue2, providerFiber);
    context._currentValue2 = nextValue;
  }
}

propagateContextChange函数很重要

假如Provider 与 Consumer 之间有一个组件使用了shouldComponentUpdate且返回false,propagateContextChange 函数能保证Consumer能接收到更新后的value。

在propagateContextChange中,以当前fiber为出发点在子树中遍历寻找匹配的Consumer节点, 一旦找到后会不断的回溯,修改祖先节点的childExpirationTime,保证相关的子树能重新render,而不会因为shouldComponentUpdate而失效。

export function propagateContextChange(
  workInProgress: Fiber,
  context: ReactContext<mixed>,
  changedBits: number,
  renderExpirationTime: ExpirationTime,
): 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.
    //在readContext方法中会设置当前fiber的dependencies属性
    const list = fiber.dependencies;
    if (list !== null) {
      nextFiber = fiber.child;

      let dependency = list.firstContext;
      while (dependency !== null) {
        // Check if the context matches.
        //context有变化就调度更新
        if (
          dependency.context === context &&
          (dependency.observedBits & changedBits) !== 0
        ) {
          // Match! Schedule an update on this fiber.

          if (fiber.tag === ClassComponent) {
            // Schedule a force update on the work-in-progress.
            const update = createUpdate(renderExpirationTime, null);
            update.tag = ForceUpdate;

            enqueueUpdate(fiber, update);
          }

          if (fiber.expirationTime < renderExpirationTime) {
            fiber.expirationTime = renderExpirationTime;
          }
          let alternate = fiber.alternate;
          if (
            alternate !== null &&
            alternate.expirationTime < renderExpirationTime
          ) {
            alternate.expirationTime = renderExpirationTime;
          }

          scheduleWorkOnParentPath(fiber.return, renderExpirationTime);

          // Mark the expiration time on the list, too.
          if (list.expirationTime < renderExpirationTime) {
            list.expirationTime = renderExpirationTime;
          }

          // Since we already found a match, we can stop traversing the
          // dependency list.
          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 (
      enableSuspenseServerRenderer &&
      fiber.tag === DehydratedSuspenseComponent
    ) {
      // If a dehydrated suspense component 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 on its children.
      if (fiber.expirationTime < renderExpirationTime) {
        fiber.expirationTime = renderExpirationTime;
      }
      let alternate = fiber.alternate;
      if (
        alternate !== null &&
        alternate.expirationTime < renderExpirationTime
      ) {
        alternate.expirationTime = renderExpirationTime;
      }
      // 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 childExpirationTime on
      // this fiber to indicate that a context has changed.
      scheduleWorkOnParentPath(fiber, renderExpirationTime);
      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;
        }
        let 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;
  }
}

ReactContext.Consumer

ReactContext.Consumer在收到需要更新的时候,会去读取context的currentValue 作为最新的 contextValue,传给子组件,同时Consumer需要建立依赖关系,修改fiber的dependencies属性

function updateContextConsumer(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  let context: ReactContext<any> = workInProgress.type;

  const newProps = workInProgress.pendingProps;
  const render = newProps.children;


  prepareToReadContext(workInProgress, renderExpirationTime);
  //读取新的value
  const newValue = readContext(context, newProps.unstable_observedBits);
  let newChildren;
//将新的value传给children
  newChildren = render(newValue);

  reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
  return workInProgress.child;
}


export function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean,
): T {

  if (lastContextWithAllBitsObserved === context) {
    // Nothing to do. We already observe everything in this context.
  } 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: any): ReactContext<mixed>);
      resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
    } else {
      resolvedObservedBits = observedBits;
    }

    let 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 = {
        expirationTime: NoWork,
        firstContext: contextItem,
        listeners: null,
        responders: null,
      };
    } else {
      // Append a new context item.
      lastContextDependency = lastContextDependency.next = contextItem;
    }
  }
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

changedBits/observedBits 粒度控制

Context 已经内置让开发者手动控制更新粒度的方式,React.createContext 的第一个参数是 defaultValue,它还拥有第二个参数 calculateChangedBits,它是一个接受 newValue 与 oldValue 的函数,返回值作为 changedBits,在 Provider 中,当 changedBits = 0,将不再触发更新。而在 Consumer 中有一个不稳定的 props,unstable_observedBits,若 Provider 的changedBits & observedBits = 0,也将不触发更新。

  • changedBits 是创建context的时候传入的calculateChangedBits返回的结果
  • observedBits是consumer 的属性unstable_observedBits,该属性需要是二进制数字

results matching ""

    No results matching ""