Throw out problem

When react is used, data is transmitted from the top down. For example, if theme data is stored in the state of the top-level component, the theme of the entire App is managed. thenState management framework without recourse to any third partyIf you want to get the theme data from a child component, you must pass it down the hierarchy, even if the component in between doesn’t need the theme data at all. As you can see in the figure below, the deeper the App is, the more layers of transfer can be a disaster for developers.

To introduce the context

Because of the need to pass values across hierarchies, the official context mechanism is also provided. By using the context, we can get the value from the ancestor component in the child component without passing it through layers. In fact, many state management frameworks and React libraries use context features, such as the famous React-Redux.

Context prior to V16.3 is an official experimental API. It is not officially recommended for developers, but many frameworks still use it. Therefore, the official release of the new Context API in V16.3, the new API will be more easy to use, this article is also based on V16.3.

In the new Context API, React provides a createContext method that returns an object containing a Provider or Consumer, and a Provoider or Consumer object is the focus of the new API.

Let’s take a look at a simple example and then go through the API. In this case, the state of the top-level parent component stores some properties that control the theme of the App, and the context is used to pass these properties across components so that the underlying component can get them directly.

We define the context in themecontext. js and export Provider and Consumer:

import {createContext} from "react";

export const {Provider, Consumer} = createContext({
  color: "green".fontSize: "20px"
});
Copy the code

CreateContext needs to pass a parameter called defaultValue. When does this value come into play? This will be explained later.

We can then use the Provider directly in the top-level App component:

import React, {Component} from 'react';
import {Provider} from "./context/themeContext";
import Parent from "./Parent";

class App extends Component {
  state = {
    color: "red".fontSize: "16px"
  };

  render() {
    return (
      <div className="App">
        <Provider value={this.state}>
          <Parent/>
        </Provider>
      </div>); }}export default App;
Copy the code

We use the Provider component directly in the top-level component, and the Provider component has a value property that passes the actual value of the context. We can then get these values to use in the underlying Child component.

Hierarchy: App -> Parent -> Child

import React, {PureComponent} from "react";
import {Consumer} from "./context/themeContext";

class Child extends PureComponent {
  render() {
    return <Consumer>
      {
        style => <div style={style}>This is Child Component that gets style value through context.</div>
      }
    </Consumer>}}export default Child;
Copy the code

Using Consumer in the Child component, you get the value of the context passed by the parent; The Consumer needs a function as a child element that takes context Value, and then returns the specific component style for that component.

This is a simple example of using context. You can see that the context API is very simple and easy to use.

  • CreateContext: Creates the context, takes a defaultValue argument, and returns an object containing Provider and Consumer
  • Provider: The top-level component that provides the context and contains props for a value, which is the actual context data
  • Consumer: The underlying component that gets the context needs a function as its child that takes a value argument that is the context value passed by the upper layer

Looking at this, you might be wondering: Why does createContext need a defaultValue when the Provider needs an actual value? When exactly does defaultValue kick in? The conclusion is that the Consumer of the underlying component passes defaultValue directly as an argument to a child function only if the upper component does not provide a Provider component. In this example, the Consumer Child in the Child component will be {color: “red”, fontSize: {color: “red”, fontSize: {color: “red”, fontSize: “16px”} defaultValue, which is not used in other cases. DefaultValue ={undefined}; defaultValue ={undefined}; Remember, you can try it out for yourself and see if this is true.

One step closer to

While using Consumer makes it easy to get the value of the context, it would be cumbersome to call Consumer and return the actual component content in its child functions to get the value of the context. So we can simply encapsulate Consumer, encapsulating a connect method. To achieve an effect similar to the connect function in React-redux. The code for the connect method is as follows:

import React from "react";
import {Consumer} from "./context";

export default mapState => {
  return WrappedComponent= > {
    const Component = props= > (<Consumer>
      {
        value => {
          let mappedProps = mapState(value);
          return <WrappedComponent {. props} {. mappedProps} / >}}</Consumer>);
    Component.displayName = `connect(${WrappedComponent.displayName || WrappedComponent.name || "Component"}) `;
    returnComponent; }};Copy the code

The mapState method is the value mapping of the context. When the connect method is called, it still returns a function. This function is a high-order function factory that wraps the passed WrappedComponent in a Consumer wrapper and uses the mapState mapping to get the props properties for the calculation. It gives those props properties to the WrappedComponent. So, when we want to get the context later, we just call this method.

Here’s another example of how to use the Connect method: Suppose you have an App user that displays information about a student; Student information includes name, age and gender. There are also two components: Student, StudentGender; Student displays the Student’s name, age, and has a + button that adds one year to the current age.

The hierarchy is App -> StudentContainer -> Student

The code for the App component is as follows:

import React, {Component} from 'react';
import {Provider} from "./context";
import StudentContainer from "./StudentContainer";

class App extends Component {

  onIncreaseAge = () = > {
    this.setState(preState= > ({
      age: preState.age + 1}})); state = {name: "Zhang".age: 12.gender: "Male".onIncreaseAge: this.onIncreaseAge
  };

  render() {
    return (
      <div className="App">
        <Provider value={this.state}>
          <StudentContainer/>
        </Provider>
      </div>); }}export default App;
Copy the code

In the App component, we pass the student property to the context along with the method to increase the age, so that the child component can either get the property or call the method to modify the property.

The code for the Student component is as follows:

import React from "react";
import {connect} from "./context";

const Student = ({studentName, studentAge, onIncreaseAge}) = > {
  return <div>
    <span className="title">Student:</span>
    <ul>
      <li>name: {studentName}</li>
      <li>age: {studentAge}
        <button onClick={onIncreaseAge}>+</button>
      </li>
    </ul>
  </div>;
};

const mapState = state= > ({
  studentName: state.name,
  studentAge: state.age,
  onIncreaseAge: state.onIncreaseAge
});
export default connect(mapState)(Student);
Copy the code

As you can see, when we use the Connect method, the Student component becomes a dumb component that only needs to focus on displaying the data.

conclusion

So that’s a brief introduction to context, and you can see that it’s really easy to pass data across hierarchies. Context is a good choice when we want to transfer data across hierarchies, but the data itself has few places to transfer, and we don’t want to introduce a more complex state management framework (such as Redux). The addresses of cases involved in this article are here. The first case is in the sample-theme branch, and the second case is in the Mosate branch.

If you have any comments or suggestions on this article, welcome to discuss and correct!!