High-level component HOC concept

The basic definition

  • Formally, it’s a function

  • Function: You take an original component (and any other data you might need) and you output a new component with some wrapping. The original component is not affected

  • It’s a factory factory.

  • Understand the concept of packaging

    • Property broker: Controls the props passed to the original component (the wrapped component)
    • Reverse inheritance: A higher-level component “inherits” the wrapped component

Higher order component cognition

  • A simple example:

    • High order componentwithSubscription
    • Two components to wrapCommentListandBlogPost
    • Functionality: The functionality of two components that require remote access to the latest data and keep it up to date is implemented in a similar way, with higher-order components encapsulated to separate their similar behavior
  • How do we pass values where higher-order components are called

// The first argument passed is the component itself, and the second argument is a function
// the DataSource is a global variable, just know that it stores the required data and can be retrieved by the internal getxx method.
//props is the props of a component, as described below
const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) = > DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) = > DataSource.getBlogPost(props.id)
);

Copy the code
  • High order componentwithSubscriptionThe implementation of the
    1. As stated earlier, the return value of the function is a new component
    2. First through the function passed inselectDataAnd received by the component constructorpropsThe required data is obtained and storedstatethedataProperties. (Due to the nature of JS, there is no problem passing more parameters than parameters.)
    3. To implement ahandleChangeMethod to get and assign values when data changes
    4. Called separately when the component is mounted and destroyedDataSourceAdd listener and remove listener methods, this isDataSourceFor checking data, don’t care, as long as you know it will keep data from remote sources up to date.
    5. Finally return the component passed in in render,
      1. And passes the retrieved data to the component.
      2. Pass the other props back as welldata1=props.data1 data2=props.data2The form of
// This function accepts a component...
function withSubscription(WrappedComponent, selectData) {
  / /... And returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      / /... Responsible for subscription related operations...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      / /... And render the wrapped component with the new data!
      // Note that we may also pass other attributes
      return <WrappedComponent data={this.state.data} {. this.props} / >; }}; }Copy the code
  • features:
    • A higher-order component is a pure function, that is, it does not change the parameters passed in and returns a completely new independent component
    • ** Since the higher-order component is a custom function, the number of arguments is not fixed. Developers are free to change the parameters of the actual function as needed. ** If you want to externally configure the name of the data property passed to the wrapped component in the above example, you can add a third parameter to the higher-order component function as the name of the wrapped component when data is passed in

Normalization and convention

Normalization: Use composition without changing the original component

  • Incorrect usage:
function logProps(InputComponent) {
  InputComponent.prototype.componentDidUpdate = function(prevProps) {
    console.log('Current props: '.this.props);
    console.log('Previous props: ', prevProps);
  };
  // Returns the original input component, indicating that it has been modified.
  return InputComponent;
}

// The enhanced component gets log output each time the logProps is called.
const EnhancedComponent = logProps(InputComponent);
Copy the code

If you have a second higher-order component that handles the component and modifies the componentDidUpdate hook, then the changes in the logProps are overwritten by the second higher-order component. As a result, only the last higher-order component to be called will ensure successful modification, causing confusion. In addition, such changes can cause you to never use the original component again.

  • Right: Use composition to encapsulate a new component and implement changes to the hook function
    • Then if you want to do something else with the original component hook function, you pass in a new higher-order component
    • If you want to modify the hook function on this basis, pass in a new higher-order function with the new component generated here
function logProps(WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('Current props: '.this.props);
      console.log('Previous props: ', prevProps);
    }
    render() {
      // Wrap the Input component in a container without modifying it. Good!
      return <WrappedComponent {. this.props} / >; }}}Copy the code

Conventions: Conventions about PORPS passthrough

The documentation on this part is a little vague, the example code given is quite abstract, and the explanation found online is not very detailed, almost copied from the documentation.

  • Personal understanding
    • HOC should be “== transparent ==”, that is, the new component with this layer of packaging does not change in actual function, but only in the way of function implementation
    • For the porps that HOC does not use and the original component needs, it should be directly passed intact
    • Do not pass external porps that are not needed by the original component
    • New methods and properties implemented in HOC (such as data obtained remotely in the previous example) are passed to the original component in addition

Convention: Maximize composability

  • The core idea is to make use of the functional nature of HOC. I understand that the purpose is to increase the ease of use when using composite.
  • In order toReact ReduxtheconnectMethods for example. (It doesn’t matter if you don’t, as long as you know that it’s a function and that its return value is a higher-order component that only takes a component as an argument — also a function. Connect is a higher-order function.
    • Features of HOC: Incoming [component] => return [component]
      • Ps: Signature, but I think it should be translated as signature… ?
    • Meaning: Multi-level nested execution. For example, if you want to go through multiple layers of encapsulation, you can call it like this
f(g(h(... args)))Copy the code

Convention: Package display name

  • Mainly for debugging convenience, setdisplayName
function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/ *... * /}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)}) `;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
Copy the code

Specification: Do not use HOC in render

  • React’s update strategy compares old and new subtrees
    • If the new and old components are equal (===), compare the different places between the subtrees for updates
    • Otherwise, discard the old subtree and use the new one
  • Therefore, if you use HOC in render, not only will you have to call HOC to recreate it every time, but you’ll also be doomed to a different result every time, unloading the old subtree and using the new one every time
  • In addition to wasting performance ==, this will cause the state of the old subtree and its subsequent children to be lost == (because the new one has been created again).

Specification: Copy static methods

  • If static methods are implemented within the component, they must be actively copied into the encapsulated component in HOC or they will not be called
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/ *... * /}
  // You must know exactly which methods to copy
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance; //Enhance is the packaged component
}
Copy the code

The disadvantage above is that you must know which methods to copy, which is cumbersome and error-prone. You can, of course, add parameters to the HOC function, pass the names of all static methods that need to be copied, and loop over

  • use hoistNonReactStaticAutomatic copy
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/ *... * /}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}
Copy the code
  • Or export these methods in addition
// Use this method instead of...
MyComponent.someFunction = someFunction;
export default MyComponent;

/ /... Export the method separately...
export { someFunction };

/ /... And import them in the components you want to use
import MyComponent, { someFunction } from './MyComponent.js';
Copy the code

Note: Refs will not be passed

  • refsandkeyEtc are not inside props and therefore cannot be passed to the original component through props
  • The solution isReact.forwardRef, obtained and passed by other means, as specified in the official documentationRef chapter