This article will start with the concept of high-order components and use application scenarios and examples to learn high-order components of React. When developing the React component, it is easy to find that some functions are common to multiple components. The most straightforward idea is to extract this common logic and reuse it. There is, of course, a way to reuse this logic as a React component. However, in some cases, this shared logic cannot be used by itself; it simply enhances the functionality of other components. At this point, we can use the “HoC” component design pattern to solve the problem.


The React component is a function that takes at least one React component as an argument and returns a new React component as a result. The newly generated React component is a wrapper around the component as a parameter. One of the simplest higher-order components looks like this:

const withDoNothing = (Component) => {
  const NewComponent = (props) => {
    return<Component {... props} />; };return NewComponent;
};
Copy the code

The function withDoNothing above is a higher-order component. As a common code specification in the industry, higher-order components are usually named with the prefix with, and the latter part of the name represents the function of the higher-order component. For the example of higher-order components above, we can summarize the basic pattern of higher-order components:

  1. Higher-order components cannot modify the component as a parameter. Higher-order components must be onePure functionsThere shouldn’t be any side effects.
  2. The result returned by higher-order components must be a new oneThe React componentsThe JSX part of the new component will definitely contain components as parameters.
  3. Higher-order components typically need to pass their props to the component as a parameter.

Extract common logic with higher-order components

Here’s a scenario: for an e-commerce site, the “Log out” button, the “shopping cart” module, will only be displayed after the user logs in. To implement the logic above, the “Log out button” would say:

// Suppose there is a function getUserId that reads the ID of the logged-in user from the cookies. If the user is not logged in, the function getUserId returns null const LogoutButton = () => {if (getUserId()) {
    return. ; // Display "log out" JSX}else {
    returnnull; }}Copy the code

Similarly, the code for “shopping cart” looks like this:

const ShoppingCart = () => {
  if (getUserId()) {
    return. ; // display "shopping cart" JSX}else {
    returnnull; }};Copy the code

The above two components obviously have the same logic, which is where the higher-order components come into play. We define a higher-order component withLogin with the following internal implementation code:

const withLogin = (Component) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return<Component {... props} />; }else {
      returnnull; }}return NewComponent;
};
Copy the code

The “shopping cart” and “exit button” modules could then look like this:

const LogoutButton = withLogin((props) => {
  return. ; // display JSX}); const ShoppingCart = withLogin(() => {return. ; // display "shopping cart" JSX});Copy the code

That way, we just need to focus on our own internal implementations.


The progression of higher-order components

####1: Accept multiple component parameters again, let’s modify withLogin, defined as withLoginAndLogout, to accept two React components, and select the appropriate component to render based on whether the user is logged in.

const withLoginAndLogout = (ComponentForLogin, ComponentForLogout) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return<ComponentForLogin {... props} />; }else {
      return <ComponentForLogout{...props} />;
    }
  }
  return NewComponent;
};
Copy the code

In this way, we can produce different content based on the user’s login status

const TopButtons = withLoginAndLogout(
  LogoutButton,
  LoginButton
);
Copy the code

####2: Chain Calls to higher-order components Chain calls to higher-order components give new life to higher-order components. Suppose that there are three higher-order components withOne, withTwo, and withThree. If you want to give a component X the superpowers of some higher-order components, then all you need to do is wrap the higher-order components one by one, as follows:

const X1 = withOne(X); const X2 = withTwo(X1); const X3 = withThree(X2); const SuperX = X3; // The final SuperX has the superpowers of three higher-order componentsCopy the code

Of course, we can simplify calling higher-order components directly and consecutively, as follows:

const SuperX = withThree(withTwo(withOne(X)));
Copy the code

For X, it is wrapped by higher-order components, and it makes no difference whether it is wrapped by one higher-order component or N higher-order components. So can we think a little bit deeper? Since the higher-order component itself is a pure function, and the pure function can be combined, we can actually combine several higher-order components into one higher-order component, and then use this combination of higher-order components to package X. Compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose compose This allows you to use only one higher-order component where multiple higher-order components are used. A method for implementing Compose is provided as a reference.

export default functioncompose(... funcs) {if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  returnfuncs.reduce((a, b) => (... args) => a(b(... args))) }Copy the code

Syntax explanation of reduce.


Every coin has two sides, and it needs to be used dialectically when looking at higher-order components. 1: Higher-order components have to deal with displayName, otherwise debug will be painful. When React renders an error, the component’s displayName static property determines the error component class. High-order components always create a new React component class, so each high-order component needs to handle the displayName. Such as:

const withExample = (Component) => {
  const NewComponent = (props) => {
    return<Component {... props} />; } NewComponent.displayName = `withExample(${Component.displayName || Component.name || 'Component'}) `;return NewCompoennt;
};
Copy the code

React lifecycle functions are not treated as special for higher-order components. However, if the inner component contains custom static functions that are called outside the React lifecycle, higher-order components must add support for these static functions to the newly generated component, which becomes more cumbersome. 3: Avoid regenerating React components. Do not use higher-order components like this

const Example = () => {
  const EnhancedFoo = withExample(Foo);
  return <EnhancedFoo />
}
Copy the code

If used as above, every time you render Example, a new component will be created with a higher-order component. Although both are called EnhancedFoo, React is completely new and will not reuse the previous virtual DOM during re-rendering, resulting in huge waste. The correct way to write it is as follows:

const EnhancedFoo = withExample(Foo);

const Example = () => {
  return <EnhancedFoo />
}
Copy the code