preface

Now that we know what higher-order functions are, higher-order components are similar to higher-order functions, which take a React component as input and output a new React component, okay

Concretely, a higher-order component is a function that takes a component and returns a new component.

When we wrap the React component in a container (W), the higher-order component returns a component that enhances (E). Higher-order components make our code more reusable, logical, and abstract. It can control props and state, and it can hijack the Render method…

It goes something like this:

const EnhancedComponent = higherOrderComponent(WrappedComponent)

A simple example:

import React, { Component } from 'react';
import ExampleHoc from './example-hoc';

class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
    <div>
       {this.props.title} - {this.props.name}
    </div>
    )
  }
}
export default ExampleHoc(UseContent)
Copy the code
import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         title: 'hoc-component',
         name: 'arcsin1',}}render() { const newProps = { ... this.state, }return<WrappedComponent {... this.props} {... this.newProps} /> } } }export default ExampleHoc
Copy the code

The component UseContent, as you can see, is a very simple render, whereas the component ExampleHoc has enhanced it, very simple functionality.

Application scenarios

I will write the following code using a decorator

Property broker. The higher-order component operates the props through the wrapped React component

Reverse inheritance. Higher-order components inherit from the wrapped React component

1. Property proxy

Small example:

import React, { Component } from 'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return( <div> {... This. Props} // This is just demo </div>)}}Copy the code
import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return<WrappedComponent {... this.props} /> } } }export default ExampleHoc
Copy the code

Such a component can then be called as an argument, and the original component has the higher-order component’s modifications to it. It’s as simple as that, preserving the encapsulation of individual components while preserving ease of use. Of course, the above lifecycle is as follows:

Didmount -> HOC didmount ->(HOCs didmount) ->(HOCs Willunmount) -> HOC Willunmount -> unmount

  • To control the props

    I can read, edit, add, and remove props from the WrappedComponent, but I need to edit and remove props carefully. We should give the props of the higher-order components a new name to prevent confusion.

    Such as:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       const newProps = {
           name: newText,
       }
       return<WrappedComponent {... this.props} {... newProps}/> } } }export default ExampleHoc
Copy the code
  • Use references through refs

    In higher-order components, we can accept refs using WrappedComponent references. Such as:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    proc = wrappedComponentInstance => {
        wrappedComponentInstance.method()
    }
    render() {
       const newProps = Object.assign({}, this.props,{
           ref: this.proc,
       })
       return<WrappedComponent {... this.newProps} /> } } }export default ExampleHoc
Copy the code

When the WrappedComponent is rendered, the refs callback is executed to get a reference to the WrappedComponent instance. This is easy to use to read and add instance props, and to call instances.

  • Abstract the state

We can abstract state by providing props and callback functions through WrappedComponent. As in our initial example, we can abstract the original component into a presentation component, separating the internal state and making it stateless.

Example:

import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         name: ' ',
        }
    }
    onNameChange = e => {
        this.setState({
            name: e.target.value,
        })
    }
    render() {
       const newProps = {
         name: {
            value: this.state.name,
            onChange: this.onNameChange,
         }
       }
       return<WrappedComponent {... this.props} {... newProps} /> } } }export default ExampleHoc
Copy the code

Above we effectively abstracted the same state operation by referring the input name prop and onchange methods to higher-order components.

Import React, {Component} from'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
        <input name="name"{this.props. Name} />)}} this is a controlled componentCopy the code
  • The other elements wrap the WrappedComponent

    Otherwise, we can wrap the WrappedComponent with other elements to add style and layout. For example,

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return (
            <div style={{display: 'flex'}}> <WrappedComponent {... this.props} /> </div> ) } } }export default ExampleHoc
Copy the code

2. Reverse inheritance

As you can see from the literal, it is related to inheritance. Let’s look at an example:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
       return super.render()
    }
  }
}
Copy the code

As you can see, higher-order components return components that inherit from WrappedComponent. Because WrappedComponent is passively inherited, all calls are reversed. So that’s where anti-generational inheritance comes in. This method is not quite the same as property proxy. It is implemented by inheriting WrappedComponent. Methods can be called sequentially through super to see the lifecycle:

Didmount -> HOC didmount ->(HOCs didmount) -> Willunmount -> HOC Willunmount ->(HOCs Willunmount)

In reverse inheritance, higher-order functions can use WrappedComponent references, which means they can use WrappedComponent’s state, props, lifecycle, and render methods. But it does not guarantee that the entire subcomponent tree will be parsed, note.

  • Rendering hijacked

Render hijacking is when higher-order components can control the WrappedComponent rendering process and render various results. We can read, add, modify, and delete props from any React element, or modify the React tree, or wrap the React tree with style controls.

We can manipulate the WrappedComponent element tree and print the correct result, but if the element tree contains a React component of function type, we cannot manipulate the component’s children.

Let’s look at a conditional rendering example:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      if(this.props. LoggedIn) {// render when loggedInreturn super.render()
      } else {
          return null
      }
      
    }
  }
}
Copy the code

Render output:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      const eleTree = super.render()
      let newProps = {}
      
      if(eleTree && eleTree.type === 'input') { 
           newProps = {value: 'This cannot be rendered'}
      } 
      const props = Object.assgin({},eleTree.props,newProps)
      const newEleTree = React.cloneElement(eleTree, props, eleTree.props.children)
      return newEleTree
      
    }
  }
}
Copy the code
  • Control of the state

    Higher-order components can read, modify, delete the state of the WrappedComponent instance, or add state if necessary, but your WrappedComponent will be a mess. Therefore, most higher-order components should restrict reading or increase state, especially by renaming state to prevent confusion.

    Take a look at some examples:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      <div>
       <h3>HOC debugger</h3>
       <p>Props <pre>{JSON.stringfy(this.props,null,1)}</pre></p>
       <p>State <pre>{JSON.stringfy(this.state,null,2)}</pre></p>
       {super.render()}
      </div>
    }
  }
}
Copy the code

Higher-order components accept other parameters

For example, I store the user information in the local LocalStorage. Of course, there are many keys in it, but I don’t need to use all of them. I want to get what I want according to my preferences.

import React, { Component } from 'react'

const ExampleHoc = (key) => (WrappedComponent) => {

  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }

    render() {
      return<WrappedComponent data={this.state.data} {... this.props} /> } } }Copy the code
import React, { Component } from 'react'

class MyComponent2 extends Component {  
  render() {
    return <div>{this.props.data}</div>
  }

}

const MyComponent2WithHOC = ExampleHoc(MyComponent2, 'data')

export default MyComponent2WithHOC

Copy the code
import React, { Component } from 'react'

class MyComponent3 extends Component {  
  render() {
    return <div>{this.props.data}</div>
  }
}
const MyComponent3WithHOC = ExampleHoc(MyComponent3, 'name')

export default MyComponent3WithHOC
Copy the code

In fact, at this point ExampleHoc is different from our original definition of higher-order components. It has become a higher-order function, but the return value of this higher-order function is a higher-order component. We can think of it as a variant form of higher-order components. Higher-order components of this form abound in third-party libraries. A good example is Connect in React-Redux. Check out the React-Redux API to find out.

Please point out any questions, thank you!

Reference:

  1. Higher-Order Components: higher-order-components

  2. React Higher Order Components in depth: React Higher Order Components in depth