React-router-dom source Code: React-router-dom

React-router-dom: BrowserRouter

Context provides a way to access data across components. It can easily access the data of other components without passing properties layer by layer between component trees

In the classic React application, the parent component passes data to the child components via props. But in some cases, some data needs to be shared between components. Context gives us a way to share data between components without passing it layer by layer in the component tree

Context

Context can share “global” data between components in the component tree. For example: login user information, user choice of theme, language and so on. In the following example, we “manually” pass the theme property from top to bottom to style the Button.

class App extends React.Component {
  render() {
    return <Toolbar theme="dark"></Toolbar>; }}function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme}></ThemedButton>
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme}></Button>; }}Copy the code

Using Context, we can avoid passing props through multiple intermediate components

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return( <ThemeContext.Provider value="dark"> <Toolbar></Toolbar> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to // pass the theme down explicitly anymore. function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; }}Copy the code

Sometimes, some data needs to be accessed by many components at different levels in the component tree. Context allows us to “broadcast” changes to data across components

The Context related API

React.createContext

const MyContext = React.createContext(defaultValue);
Copy the code

Create a new Context object. When React renders a component that registers a Context, it reads the Context value of the Provider component closest to the parent component

DefaultValue is used only if the Consumer component cannot find the Provider component.

Context.Provider

<MyContext.Provider value={/* some value */} >Copy the code

Each Context object carries a React component called Provider. The Provider enables the Consumer component to listen for changes to the context

A Provider can interact with multiple Consumer components by passing a prop of Value to the Provider’s descendant Consumer component.

All descendant Consumer components are rerendered after the Provider’s value property is updated. This update propagates from the Provider to its descendant Consumer component, but does not fire the shouldComponentUpdate method. So the Consumer component is updated even if its ancestor is not

Context uses the same algorithm as object. is to compare old and new values of a value to determine whether its value has been updated

Pay attention to

This way of determining whether a value has changed can cause problems when passing objects to value. Please attend the Caveats.

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of MyContext */
  }
  componentDidUpdate() {
    let value = this.context;
    / *... * /
  }
  componentWillUnmount() {
    let value = this.context;
    / *... * /
  }
  render() {
    let value = this.context;
    /* render something based on the value of MyContext */
  }
}
MyClass.contextType = MyContext;
Copy the code

After assigning a Context object to the contextTpe property of the class, we can use this. Context to get the methods of the current Context object in each of the component’s declaration cycles

Note:

In this way, only one Context object can be registered per component. If you need to read the value of Multiple Contexts, join Consuming Multiple Contexts.

If the syntax from the ES experiment is used in the encoding, then the contextTYpe can be initialized using a static member of the class. The code is as follows:

class MyClass extends React.Component {
 static contextType = MyContext;
 render() {
   let value = this.context;
   /* render something based on the value */}}Copy the code

Context.Consumer

<MyContext.Consumer>
  {value => /* render something based on the context value */}
</MyContext.Consumer>
Copy the code

The Consumer is a React component that listens for context changes. It allows us to listen for contxt changes in a function component.

The Consumer component requires that its child be a function. This function takes the value of the current context and requires that the React node (node) pass the value to this function equal to the context value of the outerprovider closest to Consumner. If there is no external Provider component, it is the value of the argument passed when createContext() is called (the default value of context).

Pay attention to

For more information about child elements as a function, go to Render props

chestnuts

Update the Context in the nested component

In development, we often need to update the value of the context on some deeply nested component. At this point, we can pass down a function that updates the value of the context. The code is as follows:

theme-context.js

// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: (a)= >{}});Copy the code

theme-toggler-button.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // The Theme Toggler Button receives not only the theme
  // but also a toggleTheme function from the context
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}} >
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;
Copy the code

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

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

    this.toggleTheme = (a)= > {
      this.setState(state= > ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State also contains the updater function so it will
    // be passed down into the context provider
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // The entire state is passed to the provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);
Copy the code

Using multiple Contexts

To keep React rendering fast, we need to write each consumer component as a separate component node.

// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
  name: 'Guest'});class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App component that provides initial context values
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// A component may consume multiple contexts
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}
Copy the code

If more than two contexts are often used together, we need to consider creating a Render Prop Component that provides both contexts together

Pay attention to

Because the context uses the reference identity to determine when it needs to be rerendered, there are cases where the provider’s parent element is rerendered and the consumer’s non-internal rendering is triggered. For example, the following code rerenders all consumer components each time the Provider rerenders. Because it keeps creating a new object and assigning it to value(value keeps changing)

class App extends React.Component {
  render() {
    return (
      <Provider value={{something: 'something'}} >
        <Toolbar />
      </Provider>); }}Copy the code

To avoid this problem, place value in the component’s state

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'}}; } render() {return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>); }}Copy the code