Redux-react or Mobx-React principles require you to understand the principles of the old and new context, so after a bit of research, Here we implement a simplified react Context.

New context API documentationThe official example is 👇

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar(props) { return ( <> <ThemedButton /> <ThemedColor /> </> ); Class ThemedButton extends React.Component {static contextType = ThemeContext;} class ThemedButton extends React.Component {static contextType = ThemeContext; render() { return <Button theme={this.context} />; }} // The second way to get the context, Function ThemedColor(){return (< themecontext.consumer > {value =>(<div> {value} <div/>)} </ThemeContext.Consumer> ) }Copy the code

The basic idea

A class component may have many instances, but the static attribute does not change. All instances can be fetched.

Implement the Provider and Consumer components

The createContext method exports an object that contains two high-level components: Provider and Consumer. On each update, the Provider group goes to the static getDerivedStateFromProps method, and then mounts the value of the props to the static property so that the value changes in real time. The Consumer and Provider share the same space. So every time render takes the static property value on the Provider, the two components are joined together. The second way to get the context is to make the Consumer’s children a function that passes parameters to the component to be returned for updating purposes

function createContext(defaultValue) {
    class Provider extends React.Component {
        static value = defaultValue;
        constructor(props) {
            super(props);
            Provider.value = props.value;
        }
        static getDerivedStateFromProps(nextProps, prevState) {
            Provider.value = nextProps.value;
            return prevState;
        }
        render() {
            return this.props.children; }}class Consumer extends React.Component {
        render() {
            return this.props.children(Provider.value); }}return { Provider, Consumer }
}
Copy the code

Static property to get the context value

static contextType = MyContext;

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    this.context = MyClass.contextType.Provider.value;/* This line is the mock implementation */
    let value = this.context;
    return <>
       {value}
    </>}}Copy the code

extension

The above can only run the basic demo, there are still many problems to be solved

If there are n nested levels between provider and consumer, shouldComponentUpdate is false, the bottom consumer should still receive updates.

In fact, neither the previous demo nor the older Context Legacy Context can do this, and this is one of the criticisms of the older Context, so what is the dark magic of the new Context that can do this?

The answer is that the context change causes another check

The following is somewhat cryptic, referring to Fiber’s workings: Thanks to Fiber’s linked list mechanism, all children in a Provider’s consumers register in the event pool and provide a propagateContextChange method. The Provider change triggers the updateContextProvider function, and each consumer child’s Fiber node executes the propagateContextChange method (see question 2 if the bit operation passes), In propagateContextChange, look for the matching Consumer node in the subtree rooted in the current Fiber node, and mark the update.

Therefore, shouldComponentUpdate causes the parent of the Consumer component to not be tagged, but the Provider propagateContextChange causes the Consumer component to be tagged again, So that it can be rendered.

2. What does the second argument accepted by createContext do?

Note that createContext accepts the second argument
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, // This type identifier is very special, and the first problem is related to it
        _calculateChangedBits: calculateChangedBits,
        _currentValue: defaultValue,
        _currentValue2: defaultValue,
        _threadCount: 0.Provider: (null: any),
        Consumer: (null: any),
    };

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

    context.Consumer = context;
    
    return context;
}
Copy the code

The second parameter is actually a function to determine if the context is updated, and we can use it to customize the update granularity to avoid unnecessary updates.

This is something that involves some bit operations, so I won’t go into it, but I’ll probably add it later.

CalculateChangedBits observedBits are a pair of apis, this thing may be unstable now, use with caution

Some reference articles

Different React context

ObservedBits: Secret function of React Context

React Tips — Context API (Performance Considerations) needs to be read

I see a long way to go, I will continue to search, front-end engineers are always on the road to learning