banner
SlhwSR

SlhwSR

热爱技术的一名全栈开发者
github
bilibili

Implementation and Application Scenarios of hoc and hof

1. What is it#

A higher-order function (Higher-order function) is a function that satisfies at least one of the following conditions:

  • Accepts one or more functions as input
  • Outputs a function

In React, a higher-order component accepts one or more components as parameters and returns a component. In essence, it is a function, not a component.

const EnhancedComponent = highOrderComponent(WrappedComponent);

In the above code, the function accepts a component WrappedComponent as a parameter and returns a processed new component EnhancedComponent.

This implementation of higher-order components is essentially a decorator design pattern.

2. How to write#

The basic template for writing higher-order components is as follows:

import React, { Component } from 'react';

export default (WrappedComponent) => {
  return class EnhancedComponent extends Component {
    // do something
    render() {
      return <WrappedComponent />;
    }
  }
}

By performing some operations on the incoming original component WrappedComponent (such as manipulating props, extracting state, wrapping other elements around the original component, etc.), the desired component EnhancedComponent is created.

Put common logic in higher-order components to achieve consistent processing of components and code reuse.

Therefore, the main function of higher-order components is to encapsulate and separate the common logic of components, so that the common logic can be better reused between components.

However, when using higher-order components, some conventions are generally followed, as follows:

  • Keep props consistent
  • You cannot use the ref attribute on functional (stateless) components because they do not have instances
  • Do not change the original component WrappedComponent in any way
  • Pass unrelated props attributes to the wrapped component WrappedComponent
  • Do not use higher-order components in the render() method
  • Use compose to combine higher-order components
  • Wrap display names for easy debugging

It should be noted here that higher-order components can pass all props, but not refs.

If you want to add a ref reference to a higher-order component, the ref points to the instance of the outermost container component, not the wrapped component. If you need to pass refs, use React.forwardRef, as follows:

function withLogging(WrappedComponent) {
    class Enhance extends WrappedComponent {
        componentWillReceiveProps() {
            console.log('Current props', this.props);
            console.log('Next props', nextProps);
        }
        render() {
            const {forwardedRef, ...rest} = this.props;
            // Assign forwardedRef to ref
            return <WrappedComponent {...rest} ref={forwardedRef} />;
        }
    };

    // The React.forwardRef method passes props and ref parameters to its callback function
    // So the ref here is provided by React.forwardRef
    function forwardRef(props, ref) {
        return <Enhance {...props} forwardRef={ref} />
    }

    return React.forwardRef(forwardRef);
}
const EnhancedComponent = withLogging(SomeComponent);

3. Application scenarios#

From the above understanding, higher-order components can improve code reusability and flexibility. In practical applications, they are often used for functions that are unrelated to core business but are used in multiple modules, such as permission control, logging, data validation, exception handling, and statistics reporting.

For example, if there is a component that needs to retrieve data from the cache and then render it. In general, we would write it like this:

import React, { Component } from 'react'

class MyComponent extends Component {

  componentWillMount() {
      let data = localStorage.getItem('data');
      this.setState({data});
  }
  
  render() {
    return <div>{this.state.data}</div>
  }
}

The above code can certainly achieve this functionality, but if there are other components that have similar functionality, each component needs to repeat the code in componentWillMount, which is obviously redundant.

The following can be rewritten using higher-order components, as follows:

import React, { Component } from 'react'

function withPersistentData(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem('data');
        this.setState({data});
    }
    
    render() {
      // Use {...this.props} to pass the attributes passed to the current component to the wrapped component WrappedComponent
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

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

const MyComponentWithPersistentData = withPersistentData(MyComponent2)

Another example is component rendering performance monitoring, as follows:

class Home extends React.Component {
    render() {
        return (<h1>Hello World.</h1>);
    }
}
function withTiming(WrappedComponent) {
    return class extends WrappedComponent {
        constructor(props) {
            super(props);
            this.start = 0;
            this.end = 0;
        }
        componentWillMount() {
            super.componentWillMount && super.componentWillMount();
            this.start = Date.now();
        }
        componentDidMount() {
            super.componentDidMount && super.componentDidMount();
            this.end = Date.now();
            console.log(`Rendering time of component ${WrappedComponent.name} is ${this.end - this.start} ms`);
        }
        render() {
            return super.render();
        }
    };
}

export default withTiming(Home);

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.