React series (V) — from mixin to hoc

Time:2022-6-20

Series of articles

React series (I) — 2013 origin OSCON – react architecture by vjeux

React series (II) — Realization of react basic syntax

React series (III) — JSX, synthetic events and refs

React series (IV) — Analysis on the implementation of virtualdom diff algorithm

React series (V) — from mixin to hoc

React series (VI) — from hoc to hooks

Mixins (obsolete)

This is a combination scheme provided by react at the initial stage. By introducing a common component, some life cycle operations or definition methods of the common component can be applied to achieve the purpose of extracting the common code and providing different modules for use

The previous official document demo is as follows

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.map(clearInterval);
  },
};

var TickTock = React.createClass({
  mixins: [SetIntervalMixin], // Use the mixin
  getInitialState: function() {
    return { seconds: 0 };
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // Call a method on the mixin
  },
  tick: function() {
    this.setState({ seconds: this.state.seconds + 1 });
  },
  render: function() {
    return <p>React has been running for {this.state.seconds} seconds.</p>;
  },
});

React.render(<TickTock />, document.getElementById('example'));

But mixins can only be applied tocreateClassThe creation method of class has been abandoned in the later class writing The reason is:

  1. Mixin introduces implicit dependencies
  2. Different mixins may have the problem of sequence or even code conflict coverage
  3. Mixin code can lead to snowballing complexity

For detailed introduction to mixin hazards, you can directly refer to mixins considered harmful

Higher order component

High order component (HOC) is an advanced technique for reusing component logic in react. Hoc itself is not a part of the react API. It is a design pattern based on the composite features of react.

Hoc is an advanced use method of react. The general principle is to receive a component and then return a new inherited component. There are two inheritance methods

Props proxy

The most basic implementation method

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

From the code, we can see that the attribute proxy method is actually to accept aWrappedComponentComponent is passed in as a parameter and returns an inheritedReact.ComponentComponent, and in therender()Method to return the passed inWrappedComponentassembly

Extract state & operate props

In high-order component controlstateandpropsReassign to component

import React from "react";

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        name: 'PropsProxyHOC',
      };
    }

    logName() {
      console.log(this.name);
    }

    render() {
      const newProps = {
        name: this.state.name,
        logName: this.logName,
      };
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  };
}

class Main extends React.Component {
  componentDidMount() {
    this.props.logName();
  }

  render() {
    return <div>PropsProxyHOC</div>;
  }
}

export default PropsProxyHOC(Main);

There is a common situation that is used to do

Bidirectional binding

import React, { Component } from "react";

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = { fields: {} };
    }

    //Deep update data
    onChange(fieldName, value) {
      const _s = this.state;
      this.setState({
        fields: {
          ..._s.fields,
          [fieldName]: {
            value: value,
            onChange: _s.fields[fieldName].onChange,
          },
        },
      });
    }

    getField(fieldName) {
      const _s = this.state;
      if (!_s.fields[fieldName]) {
        _s.fields[fieldName] = {
          value: "",
          onChange: (event) => {
            this.onChange(fieldName, event.target.value);
            //Reset input box
            setTimeout(() => this.onChange(fieldName, ""), 2000);
            //Forcibly trigger render
            this.forceUpdate();
          },
        };
      }

      return {
        value: _s.fields[fieldName].value,
        onChange: _s.fields[fieldName].onChange,
      };
    }

    render() {
      const newProps = {
        fields: this.getField.bind(this),
      };
      //Equivalent to injecting value, onchange attribute
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  };
}

//Retrieved ref instance component
class Main extends Component {
  render() {
    //This is equivalent to setting the value, onchange attribute
    return <input type="text" {...this.props.fields("name")} />;
  }
}

export default PropsProxyHOC(Main);

Get inherited refs instances

Because this is a new component packaged by the hoc, you need to use some special methods to obtain the refs of new components in the hoc. However, either way, you need to obtain the refs after the components are mounted And the ref attribute cannot be used on stateless components (function type components) because stateless components have no instances.

Get via parent element pass method

import React, { Component } from "react";

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    render() {
      const _p = this.props;
      //Dynamic assignment reinjection attribute
      const newProps = {};
      //Generate props instance only after listening to the corresponding method
      typeof _p.getInstance === "function" && (newProps.ref = _p.getInstance);
      return <WrappedComponent {..._p} {...newProps} />;
    }
  };
}

//Retrieved ref instance component
class Main extends Component {
  render() {
    return <div>Main</div>;
  }
}

const HOCComponent = PropsProxyHOC(Main);

class ParentComponent extends Component {
  componentWillMount() {
    console.log("componentWillMount: ", this.wrappedInstance); // componentWillMount: undefined;
  }

  componentDidMount() {
    console. log("componentDidMount: ", this.wrappedInstance); //  Componentdidmount: main instance
  }

  //Generate instances for high-level component calls
  getInstance(ref) {
    this.wrappedInstance = ref;
  }

  render() {
    return <HOCComponent getInstance={this.getInstance.bind(this)} />;
  }
}

export default ParentComponent;

Use high-level components as the middle layer

Compared with the previous method, it is necessary to provide setting assignment function in high-order components and mark a props attribute

import React, { Component } from "react";

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    //Methods exposed to components, returning ref instances
    getWrappedInstance = () => {
      if (this.props.withRef) {
        return this.wrappedInstance;
      }
    };

    //Methods exposed to components, setting ref instances
    setWrappedInstance = (ref) => {
      this.wrappedInstance = ref;
    };

    render() {
      const newProps = {};
      //Assign props instance only after listening to the corresponding method
      this.props.withRef && (newProps.ref = this.setWrappedInstance);
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  };
}

//Retrieved ref instance component
class Main extends Component {
  render() {
    return <div>Main</div>;
  }
}

const HOCComponent = PropsProxyHOC(Main);

class ParentComponent extends Component {
  componentWillMount() {
    console.log("componentWillMount: ", this.refs.child); // componentWillMount: undefined;
  }

  componentDidMount() {
    console. log("componentDidMount: ", this.refs.child.getWrappedInstance()); //  Componentdidmount: main instance
  }

  render() {
    return <HOCComponent ref="child" withRef />;
  }
}

export default ParentComponent;

Forwardref (added in 16.3)

React. Forwardref will create a react component, which can forward the ref attribute it accepts to another component under its component tree. This technique is not common, but is particularly useful in two scenarios:

  • Forward refs to DOM component
  • Forward refs in high-level components

    const FancyButton = React.forwardRef((props, ref) => (
    <button ref={ref} className="FancyButton">
      {props.children}
    </button>
    ));
    
    // You can now get a ref directly to the DOM button:
    const ref = React.createRef();
    <FancyButton ref={ref}>Click me!</FancyButton>;

    The following is a step-by-step explanation of what happened in the above example:

  • We call react Createref creates a react ref and assigns it to the ref variable.
  • We pass ref down to the JSX attribute by specifying it as a JSX attribute<FancyButton ref={ref}>
  • React passes ref to the fowardref inner function (props, ref) = >, As its second parameter.
  • We forward the ref parameter down to<button ref={ref}>, and specify it as a JSX attribute.
  • When the ref mount is completed, ref.current will point to<button>DOM node.

Hijack rendering

The simplest example is the loading component

import React, { Component } from "react";

function PropsProxyHOC(WrappedComponent) {
  return class NewComponent extends React.Component {
    render() {
      //Render interface according to state
      return this.props.isLoading ? (
        <div>Loading...</div>
      ) : (
        <WrappedComponent {...this.props} />
      );
    }
  };
}

//Retrieved ref instance component
class Main extends Component {
  render() {
    return <div>Main</div>;
  }
}

const HOCComponent = PropsProxyHOC(Main);

class ParentComponent extends Component {
  constructor() {
    super();
    this.state = {
      isLoading: true,
    };
  }

  render() {
    //Delay the main interface
    setTimeout(() => this.setState({ isLoading: false }), 2000);
    return <HOCComponent isLoading={this.state.isLoading} />;
  }
}

export default ParentComponent;

Of course, it can also be used for output nested in other elements on the layout

Inheritance inversion

The simplest demo code

function InheritanceInversionHOC(WrappedComponent) {
  return class NewComponent extends WrappedComponent {
    render() {
      return super.render();
    }
  };
}

hereWrappedComponentBecome the inherited party, so that all relevant instances of the transfer component can be obtained in the high-level component

Get inherited component instance

import React, { Component } from "react";

function InheritanceInversionHOC(WrappedComponent) {
  return class NewComponent extends WrappedComponent {
    componentDidMount() {
      console. log("componentDidMount: ", this); //  Componentdidmount: newcomponent instance
    }

    render() {
      return super.render();
    }
  };
}

//Retrieved ref instance component
class Main extends Component {
  constructor() {
    super();
    this.state = {
      name: "WrappedComponent",
    };
  }

  render() {
    return <div ref="child">Main</div>;
  }
}

export default InheritanceInversionHOC(Main);

cloneElement

Let’s explain a method of popularizing react before demo

React.cloneElement(
  element,
  [props],
  [...children]
)

withelementElement to clone the template and return a new react element.configShould include newpropskeyorref。 The props of the returned element is the result of shallow merging the new props with the props of the original element. The new child element will replace the existing child element ifconfigNot present inkeyorref, then thekeyandrefWill be retained.
React. Cloneelement() is almost equivalent to:

<element.type {...element.props} {...props}>{children}</element.type>

However, this also preserves the ref of the component. This means that when you get a child node through ref, you will not accidentally steal it from your ancestor node. The same ref will be added to the new element after cloning.

Modify props and hijack rendering

It is more complicated to modify props by reverse inheritance than by attribute inheritance

import React, { Component } from "react";

function InheritanceInversionHOC(WrappedComponent) {
  return class NewComponent extends WrappedComponent {
    constructor() {
      super();
      this.state = {
        a: "b",
      };
    }

    render() {
      //Generate instance
      const wrapperTree = super.render();
      //New attribute
      const newProps = {
        name: "NewComponent",
      };
      //Clone the wrappertree element as a template and return a new react element.
      const newTree = React.cloneElement(
        wrapperTree,
        newProps,
        //Child elements including components also need to be preserved
        wrapperTree.props.children
      );
      console.log("newTree: ", newTree);
      /* {
        type: "div"
        key: null
        ref: "child"
        props: Object
        children: "Main"
        name: "NewComponent"
        _owner: FiberNode
        _store: Object
      }*/
      return newTree;
    }
  };
}

class Main extends Component {
  render() {
    //The ref of the original element will be preserved.
    return <div ref="child">Main</div>;
  }
}

export default InheritanceInversionHOC(Main);

WhycloneElementmethod?

becauserenderFunction is actually calledReact.creatElementThe generated react element, although we can get this method, we cannot modify it AvailablegetOwnPropertyDescriptorsTo view its configuration items, use thecloneElementCreate a new element override

Compared with attribute inheritance, the latter can only conditionally choose whether to render or notWrappedComponentHowever, the former can hijack rendering elements in a more fine-grained way, and can obtainState, props, component lifecycle hook, and render method, but there is still no guaranteeWrappedComponentWhether or not the sub – Components in the are rendered cannot be hijacked

matters needing attention

Static attribute failure

//Define static functions
WrappedComponent.staticMethod = function() {/*...*/}
//Now use hoc
const EnhancedComponent = enhance(WrappedComponent);

//Enhancement component does not have staticmethod
typeof EnhancedComponent.staticMethod === 'undefined' // true

Because the high-level components are no longer the original components, the static properties of the original components cannot be obtained unless you actively copy them to the returned components

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  //You must know exactly which methods should be copied:(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

In addition to exporting components, another feasible solution is to export this static method additionally.

//Use this method instead of
MyComponent.someFunction = someFunction;
export default MyComponent;

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

// ... And in the components to be used, import them
import MyComponent, { someFunction } from './MyComponent.js';

Rendering mechanism

React’s diff algorithm (called coordination) uses component IDs to determine whether it should update an existing subtree or discard it and mount a new subtree. If the components returned from render are the same as those in the previous rendering (= = =), react recursively updates the subtree by distinguishing the subtree from the new subtree. If they are not equal, the previous subtree is unloaded completely.

Because the high-level component returns a new component, and the unique flag in it will also change, it is not recommended to call the high-level component in render, which will cause it to be unloaded and rendered again every time, even if it may be the same

render() {
  //Each time the render function is called, a new enhancedcomponent is created
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  //This will cause the subtree to be unloaded and remounted every time it is rendered!
  return <EnhancedComponent />;
}

This is not just a performance issue – reloading a component will cause the state of the component and all its subcomponents to be lost.

If you create a hoc outside the component, the component will only be created once. Therefore, each render will be the same component. Generally speaking, this is consistent with your expected performance.

Therefore, it is suggested that high-order components are pure functions without side effects, that is, the same input is always the same output, and no variable factors are allowed

Nested too deeply

In the original component, if there are too many package levels, it will cause troubles like callback hell, which is difficult to debug and has poor readability

toe the mark

If there is no specification, it may also lead to code conflict coverage, such as

  • Pass unrelated props to wrapped components
  • Maximize composability
  • Package display name for easy debugging

Recommended Today

C practice topic 96

Title:Counts the number of occurrences of substrings in a string. Program analysis:None. example: 1 #include 2 #include 3 #include 4 int main() 5 { 6 int i,j,k,TLen,PLen,count=0; 7 char T[50],P[10]; 8 printf (“please enter two strings separated by carriage return, with the parent string first and the child string last: \n”); 9 gets(T); 10 gets(P); […]