React Component design pattern – Composite components

React component design mode -Render-props

As we all know, it is very difficult and cumbersome to pass data across layers of components based on props. The middle layer components need to add useless props to pass data. React already provides the Context API to solve this problem, but it was advised not to use it before 16.3.0, because it would be deprecated sooner or later. Having said that, many libraries already use the Context API. You can see how strong the voice is. React provides a stable Context API. The examples in this article are based on the context API after V16.3.0.

concept

Start by understanding the role of context and what providers and consumers are, and think about the problem this pattern solves (cross hierarchy component communication).

All the context does is create a context object and expose the provider (usually in the upper middle of the component tree) and the consumer, and all the children of the context can access the data within the context without having to pass props. You can think of an object that centrally manages state and limits the scope within which this object can access its internal values.

The provider provides the data within the context to the consumer, and the consumer retrieves the data provided by the provider, which naturally solves the above problem.

usage

Here is a small example, the function is the theme color switch. The effect is as follows:

Based on the above concepts and functions, break down the steps to be implemented:

  • Create a context to provide to our providers and consumers
  • Provider provides data
  • Consumer acquisition data

The file organization here looks like this:

├─ Context.js // Store context files in │ ├─ index.js // Provider level │ page.js // add an intermediate level component for cross-level communication The sub-components are Title and Paragraph │ title. js // the consumer's level │ ParagraphCopy the code

Create a context

import React from 'react'

const ThemeContext = React.createContext()

export const ThemeProvider = ThemeContext.Provider
export const ThemeConsumer = ThemeContext.Consumer

Copy the code

Here, ThemeContext is a context created that contains two properties, one provider and one consumer, as the name indicates. Providers and consumers come in pairs, and each Provider has a Consumer. Each pair is created by react.createcontext ().

Page components

Nothing to say, just a container component

const Page = () => <>
  <Title/>
  <Paragraph/>
</>
Copy the code

Provider provides data

The provider is usually at a higher level, and the value the ThemeProvider accepts is the context object it provides.

// index.js
import { ThemeProvider } from './context'

render() {
  const { theme } = this.state
  return <ThemeProvider value={{ themeColor: theme }}>
    <Page/>
  </ThemeProvider>
}

Copy the code

Consumer acquisition data

In this case, the Consumer uses the renderProps model. The Consumer passes the context data as arguments to the renderProps function, so that the context data can be accessed within the function.

// Title. Js import React from'react'
import { ThemeConsumer } from './context'
class Title extends React.Component {
  render() {
    return <ThemeConsumer>
      {
        theme => <h1 style={{ color: theme.themeColor }}>
          title
        </h1>
      }
    </ThemeConsumer>
  }
}
Copy the code

About nested contexts

And now you might be wondering, you can’t just have one context in your application. So what if multiple contexts are nested?

Versions earlier than V16.3.0

React context prior to V16.3.0 was designed with this scenario in mind. It’s just a little bit of implementation trouble. To see how this works: Unlike the current version, providers and consumers are not created in pairs.

A Provider is a generic component that, of course, needs to be on top of the Consumer component. To create it, we need to use two methods:

  • GetChildContext:Its scopeContextual data
  • ChildContextTypes: statementIts scopeThe structure of the context
class ThemeProvider extends React.Component {
  getChildContext() {
    return {
      theme: this.props.value
    };
  }
  render() {
    return (
      <React.Fragment>
        {this.props.children}
      </React.Fragment>
    );
  }
}
ThemeProvider.childContextTypes = {
  theme: PropTypes.object
};
Copy the code

The consumer needs to use contextTypes to declare the structure of the received context.

const Title = (props, context) => {
  const {textColor} = context.theme;
  return<p style={{color: color}}> }; Title.contextTypes = { theme: PropTypes.object };Copy the code

Final usage:

<ThemeProvider value={{color: 'green' }} >
   <Title />
</ThemeProvider>

Copy the code

Back to the nesting problem, do you see how that works?

The Provider does two things, provides the context data, and then. It also declares the data structure of the context scope. The Consumer, on the other hand, defines the received context data structure with contextTypes. This means that the Consumer specifies which structure of data to receive, and this structure of data is defined in advance by a Provider. In this way, no amount of nesting is necessary; the Consumer simply defines the structure of the context declared by whom it receives it. If you don’t define it, you can’t receive the data of the context.

Version later than V16.3.0

Versions after V16.3.0 are much easier to use than before. There is also a more elegant way to solve the nesting problem. Because providers and consumers are created in pairs. Even if the Provider of one pair has the same data structure and value type as the Consumer of another pair, the Consumer gives access to that Provider’s context. That’s the solution.

conclusion

For this context thing. I don’t think we should use it in the app too much. Providers such as react-redux’s Provider or antd’s LocalProvider can be used almost once, because multiple uses can clutter the application and complicate dependencies between components. However, the React API is still trying to fix its state management shortcomings, which are highlighted by the Hooks useReducer.