React.createContext() for React source parsing

Posted by skyloon on Wed, 31 Jul 2019 03:51:52 +0200

Preface:
Since childContext will be discarded in React17, we will not analyze it, mainly the explanation of the new API, createContext().

1. React.createContext()

Effect:
Convenient transfer of values between ancestor components and descendant components (separated by many layers of components)

Use:
context.js:

import React from 'react';

const contextTestOne={
  name:'chen',
  length:22,
}

export const wrapContext=React.createContext(contextTestOne.name)

Ancestral Components:

import { wrapContext } from '@/utils/context';

const Father=props=>{
  return (<wrapContext.Provider value={'this is provider'}>
     <Child />
</wrapContext.Provider>)
}

The descendant component:

import { wrapContext } from '@/utils/context';

const getProviderValue=()=>{
  return <wrapContext.Consumer>{value=><span>{value}</span>}</wrapContext.Consumer>
}

const Child=props=>{
return (
    getProviderValue()
  );
}

Result:

Be careful:
When undefined is passed to the value of <Provider>, defaultValue in createContext does not take effect, and Consumer's value displays null values.

React official documents:
https://zh-hans.reactjs.org/docs/context.html#contextprovider

Source code:

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import {REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';

import type {ReactContext} from 'shared/ReactTypes';

import warningWithoutStack from 'shared/warningWithoutStack';
import warning from 'shared/warning';

export function createContext<T>(
  defaultValue: T,
  //Computing the differences between old and new context s using Object.is()
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    //No look
    if (__DEV__) {
      warningWithoutStack(
        calculateChangedBits === null ||
          typeof calculateChangedBits === 'function',
        'createContext: Expected the optional second argument to be a ' +
          'function. Instead received: %s',
        calculateChangedBits,
      );
    }
  }

  const context: ReactContext<T> = {
    //Again, $typeof in ReactContext is
    // Stored as an object in the attribute type in createElement, it's not $typeof in ReactElement.
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    //As a solution to support multiple concurrent renderers, we classify some renderers as main renderers and others as auxiliary renderers.
    // As a workaround to support multiple concurrent renderers, we categorize
    // some renderers as primary and others as secondary.

    //We just want to have at most two concurrent renderers: React Native (primary) and Fabric (secondary);
    // React DOM (primary) and React ART (secondary).
    // The auxiliary renderer stores its context value in a separate field.
    // We only expect
    // there to be two concurrent renderers at most: React Native (primary) and
    // Fabric (secondary); React DOM (primary) and React ART (secondary).
    // Secondary renderers store their context values on separate fields.

    //Value in <Provider value={xxx}> is assigned to _current Value.

    //That is to say _currentValue and _currentValue2 work the same way, except for the main and auxiliary renderers, respectively.
    _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.

    //The number of concurrent renderers used to track the context
    _threadCount: 0,
    // These are circular
    Provider: (null: any),
    Consumer: (null: any),
  };
  //const obj={}
  //obj.provider._obj = obj
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;
  //No look
  if (__DEV__) {
    // A separate object, but proxies back to the original context object for
    // backwards compatibility. It has a different $$typeof, so we can properly
    // warn for the incorrect usage of Context as a Consumer.
    const Consumer = {
      $$typeof: REACT_CONTEXT_TYPE,
      _context: context,
      _calculateChangedBits: context._calculateChangedBits,
    };
    // $FlowFixMe: Flow complains about not setting a value, which is intentional here
    Object.defineProperties(Consumer, {
      Provider: {
        get() {
          if (!hasWarnedAboutUsingConsumerProvider) {
            hasWarnedAboutUsingConsumerProvider = true;
            warning(
              false,
              'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Provider> instead?',
            );
          }
          return context.Provider;
        },
        set(_Provider) {
          context.Provider = _Provider;
        },
      },
      _currentValue: {
        get() {
          return context._currentValue;
        },
        set(_currentValue) {
          context._currentValue = _currentValue;
        },
      },
      _currentValue2: {
        get() {
          return context._currentValue2;
        },
        set(_currentValue2) {
          context._currentValue2 = _currentValue2;
        },
      },
      _threadCount: {
        get() {
          return context._threadCount;
        },
        set(_threadCount) {
          context._threadCount = _threadCount;
        },
      },
      Consumer: {
        get() {
          if (!hasWarnedAboutUsingNestedContextConsumers) {
            hasWarnedAboutUsingNestedContextConsumers = true;
            warning(
              false,
              'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
                'a future major release. Did you mean to render <Context.Consumer> instead?',
            );
          }
          return context.Consumer;
        },
      },
    });
    // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
    context.Consumer = Consumer;
  }

  else {
    //const obj={}
    //obj.consumer=obj
    //That is, the Consumber object points to the React.Context object

    //In order to ensure that Consumer gets the latest value when rendering <Consumer>,
    //Let Consumer=React.Context directly,
    // The _current Value in React.Context has been assigned to the value of <Provider>.
    //So Consumer can get the latest value right away.
    context.Consumer = context;
  }
  //No look
  if (__DEV__) {
    context._currentRenderer = null;
    context._currentRenderer2 = null;
  }

  return context;
}

Analysis:
Without looking at _DEV_, it's still quite simple. It's important to note that context.Consumer = context, so that < Consumer > equals React.context, so that you can get the latest value provided by < Provider > immediately.

2. Why abandon childContext?
Because childContext has a great impact on the underlying components, even if the child Context is not used by the descendant components, the descendant components still need to Update, seriously affecting performance.

(End)

Topics: Javascript React Attribute