Context is a very powerful API that is not used directly in many cases. Instead of using createContext directly and passing data down, most projects use a third-party library (react-Redux).

Think about how often you use @connect(…) in your project. (Comp) and ?

What is the Context

Context provides a way to pass data across the component tree without manually adding props for each layer of components.

A top-level data that wants to be passed layer by layer to some deep component would be cumbersome to pass through props. Using Context avoids explicitly passing props layer by layer through the component tree.

Context Usage Example

import React, { Component, createContext, useConText } from 'react'
const ColorContext = createContext(null)
const { Provider, Consumer } = ColorContext

console.log('ColorContext', ColorContext)
console.log('Provider', Provider)
console.log('Consumer', Consumer)

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      color: 'red'.background: 'cyan',
    }
  }
  render() {
    return <Provider value={this.state}>{this.props.children}</Provider>}}function Article({ children }) {
  return (
    <App>
      <h1>Context</h1>
      <p>hello world</p>
      {children}
    </App>)}function Paragraph({ color, background }) {
  return (
    <div style={{ backgroundColor: background}} >
      <span style={{ color}} >text</span>
    </div>)}function TestContext() {
  return (
    <Article>
      <Consumer>{state => <Paragraph {. state} / >}</Consumer>
    </Article>
  )
}

export default TestContext
Copy the code

What the page looks like

Print ColorContext, Provider, and Consumer

createContext

// createContext allows us to implement state management
// It can also solve the problem of passing Props drilling
// If a child component requires a property of the parent component, but there are several layers between them, there is a development and maintenance cost. At this point, the API can be used to solve the problem
function createContext(defaultValue, calculateChangedBits) {
  var context = {
    ? typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    // As a workaround to support multiple concurrent renderers, we categorize
    // some renderers as primary and others as secondary. 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.
    // The following two attributes are intended for multiple platforms
    _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.Consumer: null
  };

  // The following code is simple: mount the Provider and Consumer on the context for external use
  context.Provider = {
    ? typeof: REACT_PROVIDER_TYPE,
    _context: context
  };

  var Consumer = {
    ? typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

  context.Consumer = Consumer;
  context._currentRenderer = null;
  context._currentRenderer2 = null;
  return context;
}
Copy the code

The React package only generates a few objects, which is relatively simple. Here’s where it works.

Debugger in Consumer Children’s anonymous function.

View the call stack

NewChildren = render(newValue); Render is children, and newValue is assigned from the Provider value property.

newProps

newValue

Now look at the implementation of readContext

let lastContextDependency: ContextDependency<mixed> | null = null;
let currentlyRenderingFiber: Fiber | null = null;
// In prepareToReadContext
currentlyRenderingFiber = workInProgress;


export function readContext<T> (context: ReactContext
       
        , observedBits: void | number | boolean,
       ) :T {
    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.contextDependencies = {
        first: contextItem,
        expirationTime: NoWork,
      };
    } else {
      // Append a new context item.lastContextDependency = lastContextDependency.next = contextItem; }}// isPrimaryRenderer is true, which is defined as true
  Context._currentvalue is always returned
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
Copy the code

Skip the middle, the last line return context._currentValue, and

I’ve got the value of the context that’s passed down from the top

Why context can be passed from top to bottom is not clear, but I will write another article to explain it after I get familiar with the implementation of cross-component passing.

The design of the Context is very special

Provider Consumer are two properties of the context.

  var context = {
    ? typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: null.Consumer: null
  };
Copy the code

The Provider? Typeof is REACT_PROVIDER_TYPE, which takes a _context property and points to the context itself, i.e. its son has a property pointing to itself!!

  context.Provider = {
    ? typeof: REACT_PROVIDER_TYPE,
    _context: context
  };
Copy the code

The Consumer? Typeof is REACT_CONTEXT_TYPE, which also has a _context property, and its own son has a property pointing to itself!!

  var Consumer = {
    ? typeof: REACT_CONTEXT_TYPE,
    _context: context,
    _calculateChangedBits: context._calculateChangedBits
  };

Copy the code

So you can assume that the new value given by the Provider’s value property must be passed to the context via the _context property, changing the _currentValue. Similarly, the Consumer gets the _currentValue of the context based on _context and then render(newValue) executes the children function.

useContext

UseContext is a function provided by React hooks to simplify context retrieval.

Let’s look at the usage code

import React, { useContext, createContext } from 'react'
const NameCtx = createContext({ name: 'yuny' })
function Title() {
  const { name } = useContext(NameCtx)
  return <h1># {name}</h1>
}
function App() {
  return (
    <NameCtx.Provider value={{ name: 'lxfriday' }}>
      <Title />
    </NameCtx.Provider>
  )
}
export default App
Copy the code

We initially gave {name: ‘yuny’}, but then reassigned {name: ‘lxFriday ‘}, and the final page displayed lxFriday.

UseContext related source code

Take a look at useContext exported from the React package

/ * * * * @ useContext param Context {ReactContext} createContext returned results * @ param unstable_observedBits {number | Boolean | UseContext () second argument is reserved for future * @returns {*} returns the value of the context */
export function useContext<T> (
  Context: ReactContext<T>,
  unstable_observedBits: number | boolean | void.) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useContext(Context, unstable_observedBits);
}
Copy the code
// Invalid hook call. Hooks can only be called inside of the body of a function component. 
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
Copy the code
/** * Keeps track of the current dispatcher. */
const ReactCurrentDispatcher = {
  /** * @internal * @type {ReactComponent} */
  current: (null: null | Dispatcher),
};
Copy the code

Look at Dispatcher. It’s all about React Hooks.

To react – the reconciler/SRC/ReactFiberHooks. Js, HooksDispatcherOnMountInDEV and HooksDispatcherOnMount, InDEV should be used in the development environment, but not in production.

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
};

HooksDispatcherOnMountInDEV = {
   // ... 
   useContext<T>(
      context: ReactContext<T>,
      observedBits: void | number | boolean,
    ): T {
      returnreadContext(context, observedBits); }},Copy the code

Above useContext returns the value of the context via readContext, which is described above.

The debugger looks at the call stack

The initial useContext

In HooksDispatcherOnMountInDEV

In the readContext

After the above source code detailed analysis, we should understand the context creation and context value, context design is really very good!!


Welcome to follow my public account Yunying Sky

Regularly parse React source code in depth