Higher order component learning

Posted by hostcord on Sun, 16 Jan 2022 07:35:09 +0100

Before understanding higher-order components, you need to understand the lower higher-order functions, because they are very similar.

Higher order function: accept function as parameter or return value as function. The common filter, map and reduce functions in js are high-order functions, including memo functions.

High order component: the high-order component itself is not a component, but a function. It can accept a component as a parameter and return a new component. The high-order component is not a part of the react API. It is a design pattern based on the combination characteristics of react. High order components are very common in some third-party libraries of react, such as connect in redux and withRouter in react router. After we pass in the component as a parameter, it is equivalent to hijacking the component. Can we consider doing something. However, after the appearance of hooks, higher-order components are used less and less.

Basic application

import React, { PureComponent } from 'react'

class App extends PureComponent {
  render() {
    return (
      <div>
        App: {this.props.name}
      </div>
    )
  }
}

//Use of combined class components 
//First pass in a component as a parameter
function enhanceComponent(WrappedComponent) {
  //Finally, another component is returned
  class NewComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }

  //You can also rename the name of the component to be returned
  NewComponent.displayName = "Kobe";
  return NewComponent;
}

//Combined with the use of function components
function enhanceComponent2(WrappedComponent) {
  function NewComponent(props) {
    return <WrappedComponent {...props}/>
  }

  NewComponent.displayName = "Kobe";
  return NewComponent;
}

//Use high-level components to package the exported components before exporting
const EnhanceComponent = enhanceComponent2(App);

export default EnhanceComponent;

Specific application scenarios:

1. Enhanced props mode 1:

In order to reflect the advantages of high-level components, we first use context to share data.

import React, { PureComponent, createContext } from 'react';

// Create a Context object and assign default values
const UserContext = createContext({
  nickname: "default",
  level: -1,
  region: "China"
});
class Home extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>Home: {`nickname: ${user.nickname} Grade: ${user.level} region: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class About extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>About: {`nickname: ${user.nickname} Grade: ${user.level} region: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider value={{nickname: "why", level: 90, region: "China"}}>
          <Home/>
          <About/>
        </UserContext.Provider>
      </div>
    )
  }
}

export default App;

After reading the above code, we will use the high-level components:

import React, { PureComponent } from 'react';

// Define a higher-order component
function enhanceRegionProps(WrappedComponent) {
  return props => {
    //Here, we can directly share the data of region without passing it separately
    return <WrappedComponent {...props} region="China"/>
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home: {`nickname: ${this.props.nickname} Grade: ${this.props.level} region: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`nickname: ${this.props.nickname} Grade: ${this.props.level} region: ${this.props.region}`}</h2>
  }
}


const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname="coderwhy" level={90}/>
        <EnhanceAbout nickname="kobe" level={99}/>
      </div>
    )
  }
}

export default App;

Finally, we can also combine the two.

import React, { PureComponent, createContext } from 'react';

// Define a higher-order component
function withUser(WrappedComponent) {
  //Here, the shared logic is extracted directly, instead of sharing in each component
  return props => {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <WrappedComponent {...props} {...user}/>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

// Create Context
const UserContext = createContext({
  nickname: "default",
  level: -1,
  region: "China"
});

class Home extends PureComponent {
  render() {
    return <h2>Home: {`nickname: ${this.props.nickname} Grade: ${this.props.level} region: ${this.props.region}`}</h2>
  }
}

class About extends PureComponent {
  render() {
    return <h2>About: {`nickname: ${this.props.nickname} Grade: ${this.props.level} region: ${this.props.region}`}</h2>
  }
}

class Detail extends PureComponent {
  render() {
    return (
      <ul>
        <li>{this.props.nickname}</li>
        <li>{this.props.level}</li>
        <li>{this.props.region}</li>
      </ul>
    )
  }
}

const UserHome = withUser(Home);
const UserAbout = withUser(About);
const UserDetail = withUser(Detail);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider value={{nickname: "why", level: 90, region: "China"}}>
          <UserHome/>
          <UserAbout/>
          <UserDetail/>
        </UserContext.Provider>
      </div>
    )
  }
}

export default App;

2. Login authentication

We know that login is required when browsing some pages. In order to realize this requirement, we can't add login authentication operations to page components one by one; At this time, we can use the high-level components. We write the authentication operation into the high-level components, so that when export ing the relevant page components that need authentication operation, we can directly use our high-level components.

import React, { PureComponent } from 'react';

class LoginPage extends PureComponent {
  render() {
    return <h2>LoginPage</h2>
  }
}

function withAuth(WrappedComponent) {
  const NewCpn = props => {
    const {isLogin} = props;
    if (isLogin) {
      return <WrappedComponent {...props}/>
    } else {
      return <LoginPage/>
    }
  }

  NewCpn.displayName = "AuthCpn"

  return NewCpn;
}

// Shopping cart components
class CartPage extends PureComponent {
  render() {
    return <h2>CartPage</h2>
  }
}

const AuthCartPage = withAuth(CartPage);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <AuthCartPage isLogin={true}/>
      </div>
    )
  }
}

3. Hijack life cycle function

Higher order components are not used

import React, { PureComponent } from 'react';

class Home extends PureComponent {

  // About to render get a time beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // Get another time endTime when rendering is complete
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`Home render time : ${interval}`)
  }

  render() {
    return <h2>Home</h2>
  }
}

class About extends PureComponent {
  // About to render get a time beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // Get another time endTime when rendering is complete
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`About render time : ${interval}`)
  }

  render() {
    return <h2>About</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <About />
      </div>
    )
  }
}

Use high-order components (also a common extraction for the operation of obtaining component rendering time)

import React, { PureComponent } from 'react';

function withRenderTime(WrappedComponent) {
  return class extends PureComponent {
    // About to render get a time beginTime
    UNSAFE_componentWillMount() {
      this.beginTime = Date.now();
    }

    // Get another time endTime when rendering is complete
    componentDidMount() {
      this.endTime = Date.now();
      const interval = this.endTime - this.beginTime;
      console.log(`${WrappedComponent.name}render time : ${interval}`)
    }

    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}

class About extends PureComponent {
  render() {
    return <h2>About</h2>
  }
}

const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <TimeHome />
        <TimeAbout />
      </div>
    )
  }
}

Topics: React