More quickly to help you understand React - higher-order components

Keywords: Front-end React axios Vue

When it comes to react, the first thing we think of should be components. In react's eyes, everything is components. Even axios, which we use to get data, can be represented by components... for example, we can encapsulate them in this way.

<Request
  instance={axios.create({})} /* custom instance of axios - optional */
  method="" /* get, delete, head, post, put and patch - required */
  url="" /*  url endpoint to be requested - required */
  data={} /* post data - optional */
  params={} /* queryString data - optional */
  config={} /* axios config - optional */
  debounce={200} /* minimum time between requests events - optional */
  debounceImmediate={true} /* make the request on the beginning or trailing end of debounce - optional */
  isReady={true} /* can make the axios request - optional */
  onSuccess={(response)=>{}} /* called on success of axios request - optional */
  onLoading={()=>{}} /* called on start of axios request - optional */
  onError=(error)=>{} /* called on error of axios request - optional */
/>

In the project, we can write like this.

import { AxiosProvider, Request, Get, Delete, Head, Post, Put, Patch, withAxios } from 'react-axios'
...
render() {
  return (
    <div>
      <Get url="/api/user" params={{id: "12345"}}>
        {(error, response, isLoading, makeRequest, axios) => {
          if(error) {
            return (<div>Something bad happened: {error.message} <button onClick={() => makeRequest({ params: { reload: true } })}>Retry</button></div>)
          }
          else if(isLoading) {
            return (<div>Loading...</div>)
          }
          else if(response !== null) {
            return (<div>{response.data.message} <button onClick={() => makeRequest({ params: { refresh: true } })}>Refresh</button></div>)
          }
          return (<div>Default message before request is made.</div>)
        }}
      </Get>
    </div>
  )
}

It's a bit over the top... At least I think it's better to follow my own code habits. If all components handle requests like this, including some simple get requests, I don't think it's really necessary, and some of our general API s are not well managed.

So, what exactly is a higher-order component?

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

Right-click translation - --> A higher-order component is a function that accepts a component as a parameter and returns a new component.
Well, it looks so simple, but it's also practical.

1. To be specific, let's take a high-order function as an example, showUserPermit and showUserVipInfo. The two functions read user VIP from the local Storage first, and then do some processing for user VIP.

function showUserPermit() {
   let vip = localStorage.getItem('u_V');
   console.log(`You can enjoy it. ${u_V}Privileges...`);
}

function showUserVipInfo() {
   let vip = localStorage.getItem('u_V');
   console.log(`Your current situation VIP Grade ${u_V},Upgrade immediately...`);
}

showUserPermit();
showUserVipInfo();

2. We found that two of the two API s have exactly the same code, which is very redundant. This is not good. Let's change it.

function showUserPermit(u_V) {
   console.log(`You can enjoy it. ${u_V}Privileges...`);
}

function showUserVipInfo(u_V) {
   console.log(`Your current situation VIP Grade ${u_V},Upgrade immediately...`);
} 

3. It seems a little simpler to write this way, but the two API s have to depend on the parameter u_V to ensure their functions. All of us have to get this parameter before calling these two functions. This is a bit of coupling, so let's revamp it again.
function showUserPermit(u_V) {

   console.log(`You can enjoy it. ${u_V}Privileges...`);
}

function showUserVipInfo(u_V) {
   console.log(`Your current situation VIP Grade ${u_V},Upgrade immediately...`);
}

function wrapU_V(wrappedFunc) {
    let newFunc = () => {
        let vip = localStorage.getItem('u_V');
        wrappedFunc(vip);
};
    return newFunc;
}

module.exports = { 
    showUserPermit: wrapU_V(showUserPermit), 
    showUserVipInfo: wrapU_V(showUserVipInfo)
}

4. wrapU_V is a higher-order function without any side effects, so what is its meaning? What else did you do? It helps us deal with u_V, and calls the objective function (function parameters), so when you use the exported showUser Permit again, you don't have to care about how u_V comes from, what external conditions are needed, you just know that it can help me achieve what I want to do! It also eliminates the need to look at its parameters before each call. How come? It doesn't even need to care about how wrapU_V is implemented internally. Array.map, setTimeout can be called higher-order functions.

High-order components
A higher-order component is a pure function without side effects. A pair is a function.
Let's reconstruct the above example with component

import React, {Component} from 'react'
...

class showUserPermit extends Component {
    constructor(props) {
        super(props);
        this.state = {
            VIP: ''
        }
    }

    componentWillMount() {
        let VIP = localStorage.getItem('u_V');
        this.setState({
            VIP
        })
    }

    render() {
        return (
            <div>showUserPermit... {this.state.VIP}</div>
        )
    }
}

export default showUserPermit;

/* - */

import React, {Component} from 'react'
...

class showUserVipInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            VIP: ''
        }
    }

    componentWillMount() {
        let VIP = localStorage.getItem('u_V');
        this.setState({
            VIP
        })
    }

    render() {
        return (
            <div>showUserVipInfo... {this.state.VIP}</div>
        )
    }
}

export default showUserVipInfo;

The problems just discovered can be mapped into these two components.
Let's deal with it in the light of the above ideas.

import React, {Component} from 'react'

module.exports = Wrap: (WrappedComponent) => {
    class reComponent extends Component {
        constructor() {
            super();
            this.state = {
                VIP: ''
            }
        }

        componentWillMount() {
            let VIP = localStorage.getItem('u_V');
            this.setState({
                VIP
            })
        }

        render() {
            return <WrappedComponent VIP={this.state.VIP}/>
        }
    }

    return reComponent
}

Let's simplify showUserVipInfo and showUserPermit components


import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserPermit extends Component {

    render() {
        return (
            <div>welcome {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserPermit;  

/*--*/
import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserPermit extends Component {

    render() {
        return (
            <div>welcome {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserPermit; 

And higher-order components can distribute multiple target components, for example, in our project


The time selection component in the upper right corner and the echarts component are two distinct identity-specific behaviors and styles, and the others are exactly the same, including state and sharing methods. Upper Code

render() {
  return (
    <div className="mk-genData home-module-common">
      <div className="module-header">
        <div className="module-title">...</div>
        <**GenTimerComponent** receiveTimeChange={this.getData.bind(this)}/>
      </div>
      <div className="genData-nav">
        ...
      </div>
      <div>
        <**EchartsComponent** chartData={this.state.chartData}/>
      </div>
    </div>
  )

GenTimerComponent, and EchartsComponent are both target components, which we export in this way

Let's be clear. In fact, it's to separate the two components from each other where they are the same or are likely to be used. To make an aside, it's actually a'higher-order component'that nests the target component, but the regenerated new component inherits the target component instead. It looks like a control inversion, and extend+minix in Vue. Similarly, by inheriting the target component, we can get some static methods, including life cycle, state,fun.
Now understand the connect function of react-redux~
The state and action functions of redux are created and injected into Component through props.
You can use this.props directly to call redux state and action to create functions in the target component Component.

ConnectedComment = connect(mapStateToProps, mapDispatchToProps)(Component);  

Equivalent to

// connect is a function of a return function (that is, a higher-order function)
const enhance = connect(mapStateToProps, mapDispatchToProps);
// The function returned is a higher-order component that returns a function with Redux store
// New components associated
const ConnectedComment = enhance(Component);        

The same is true of antd's Form

const WrappedNormalLoginForm = Form.create()(NormalLoginForm);   

Summary: High-order components are a good way to refactor React code at a higher level. If you want to streamline your state and lifecycle methods, high-order components can help you extract reusable functions. Generally speaking, high-order components can be accomplished by nesting + inheritance. It is easier to understand by nesting + inheritance. Especially when reconstructing a complex component, this way is often faster and easier to split. As for which is the best one to use, it also depends on the business scenario. Welcome to discuss and exchange.

Posted by Plug-in on Sun, 21 Jul 2019 23:22:26 -0700