Component abstraction refers to making different components share the same class of functions, which can be called component function reuse. There are many abstraction methods based on different design concepts. For React, there are two main methods: mixin and higher-order components. Mixins are available in createClass but have been deprecated in ES6 classes (because of the side effects), but we can use mixins in ES6 by encapsulating mixins with decorator syntax sugar. The more commonly used abstraction approach is the higher-order component approach, which not only reduces the amount of code but also separates the request logic from the presentation logic for encapsulation at different levels, providing better support for independent management and testing.

The structure of the article is shown below

Mixins method

Encapsulate mixin methods

const mixin=function(obj,mixins){
    const newObj=obj;
    newObj.prototype=Object.create(obj.prototype);
    
    for(let prop in mixins){
      if(mixins.hasOwnProperty(prop)){ newObj.prototype[prop]=mixins[prop]; }}return newObj;
  }
  
  const BigMixin={
    fly:(a)= >{
    console.log('I can fly'); }};const Big=function(){
  console.log('new big');
};

const FlyBig=mixin(Big,BigMixin);

const flyBig=new FlyBig();//=>'new big'
flyBig.fly();//=>'I can fly'
Copy the code

Mixin method is to use the assignment method to mount the methods in the mixin object to the original object, so as to realize the mixing of objects.

Use mixins in React

As a typical example, many components have a requirement to update the interface periodically. The first choice is to use setInterval() to implement the timer operation, but also to remove the timer in time to reduce memory overhead, especially when a large number of components contain timers, we can use mixin sharing mechanism to solve this problem

var SetInterValMixin={
    componentWillMount:function(){
       this.intervals=[];
     },
     setInterval: function(){
       this.intervals.push(setInterval.apply(null.arguments));
     },
     componentWillUnmount: function(){
      this.intervals.map(clearInterval); }}; val TickTock=React.createClass({mixins:[SetIntervalMixin],
  getInitialState: function(){
       return {seconds: 0};
  },
  componentDidMount: function(){
    this.setInterval(this.tick,1000);
  },
  tick: function(){
    return(
    <p>React has run {this.state.seconds} seconds.</p>); }}); React.render(<TickTock/>.document.getElementById('reactContainer'));Copy the code

Simply put, functions defined in mixins are intermixed with component instances, and multiple components defining the same mixins cause the components to have some common behavior.

SetIntervalMixin also defines componentWillMount, in which case React takes precedence over componentWillMount in the mixin. If multiple mixins are defined in a mixin, they are executed in the declared order, ending with the function of the component itself.

If a component uses more than one mixin, and more than one Minxin defines the same lifecycle methods, all of these lifecycle methods are executed: first the methods in the mixin are executed in the order introduced in the mixin, and finally the methods defined in the component.

Use mixins in ES6

We know that ES6 does not support mixins, but we can use decorators to encapsulate mixins. To encapsulate mixins on top of classes, we need to get to the essence of class. ES6 does not change the prototype-based nature of JavaScript’s object-oriented approach, but it does provide some syntactic sugar on top of it, and class is one of them. So we can also implement mixins on a class with another syntactic sugar decorator. The Co-decorators library provides several decorators that implement **@mixin**:

import {getOwnPropertyDescriptors} from './private/utils';

const {defineProperty}=Object;

function handleClass(target,mixins){
  if(! mixins.length){throw new SyntaxError('@mixin() class ${target.name} requires at least one mixin as an argument');
}

for(let i=0,l=mixins.length; i<l; i++){const decs=getOwnPropertyDescriptors[mixins[i]]);
   
   for(const key in decs){
      if(! (keyintarget.prototype)){ defineProperty(target.protype,key,decs[key]); }}}}export default function mixin(. mixins){
   if(typeof mixins[0= = ='function') {return handleClass(mixins[0], []); }else{
   return target= >{
    returnhandleClass(target,mixins); }; }}Copy the code

The idea is to superimpose each mixin object’s method onto the target object’s prototype. Note defineProperty, which is a definition rather than assignment like before (the official createClass mixin method). Definitions don’t override existing methods, but assignments do.

Using the @ mixins:

import React, {component} from 'React';
import {mixin} from 'core-decorators';

const PureRender={
   shouldComponentUpdate(){}
}

const Theme={
    setTheme(){}
}

@mixin(PureRender,Theme)
class MyComponent extends Component{
    render(){}
}
Copy the code

The problem of a mixin

  • Destroy the packaging of original components

  • Naming conflicts

  • Add complexity

    In response to these issues, the React community has come up with higher-order components to replace mixins.

High order Component (HOC)

When the React component is wrapped, the higher-order component returns an enhanced React component. There are two ways to implement higher-order components, property brokering and reverse inheritance. The function of a high-order component is simply to exert control over the wrapped component, and then you can do interesting things like control the state of the wrapped component, props, abstract the wrapped component (you can abstract the wrapped component into a presentation component), and flip the element tree without side effects.

The property broker

The React component is used to pass functions to the React component through the higher-order component. The original component is not aware of the presence of the higher-order component, and only needs to overlay functions on it. This avoids the side effects of mixins. Property brokers have several common functions:

  1. To control the props

    We can read, add, edit, or delete props passed in from the WrappedComponent, but we need to be careful to delete and edit important props.

    import React,{Component} from 'React';
    
    const MyContainer=(WrappedComponent) = >
       class extends Component{
           render(){
               const newProps={
                   text:newText,
               };
               return <WrappedComponent {. this.props} {. newProps} / >; }}Copy the code
  2. Use references through refs

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

    import React,{component} from 'React';
    
    const MyContainer=(WrappedComponent) = >
       class extends Component{
           proc(wrappedComponentInstance){
               wrapperComponentInstance.method();
           }
       
       render(){
           const props=object.assign({},this.props,{
               ref:this.proc.bind(this})),return <WrappedComponent {. props} / >; }}Copy the code

    When the WrappedComponent is rendered, the refs callback is executed to get a reference to the WrappedComponent instance.

  3. Abstract the state

    That is, higher-order components can abstract the original component into presentation components, separating the internal state. We can abstract state through the props and callback functions provided by WrappedComponent. This means that the state of the original component is abstracted into the higher-order component.

    import React,{Component} from 'React';
    
    const MyContainer=(WrappedComponent) = >
      class extends Component{
          constructor(props){
              super(props);
              thisThe state = {name:' '.};this.onNameChange=this.onNameChange.bind(this);
          }
           onNameChange(event){
               this.setState({
                   name:event.target.value,
               })
           }
           
           render(){
               const newProps={
                   name: {value:this.state.name,
                       onChange:this.onNameChange,
                   },
               }
               return <WrappedComponent {. this.props} {. newProps} / >; }}Copy the code

    We extract the onChange method on Name Prop from the Input component into the higher-order component, effectively abstracting the same state operation. It can be used like this:

    import React,{Component} from 'React';
    
    @MyContainer
    class MyComponent extends Component{
        render(){
            return <input name="name" {. this.props.name} / >; }}Copy the code

  4. Wrap the WrappedComponent with other elements

    For example, we can add a style

    import React,{Component} from 'React';
    
    const MyContainer=(WrappedComponent) = >
      class extends Component{
          render(){
              return(
              <div style={{display:'block'}} >
                 <WrappedComponent {. this.props} / >
               </div>)}}Copy the code

    Reverse inheritance

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

    A higher-order component returns a component that inherits from WrappedComponent. It can use WrappedComponent’s state, props, lifecycle, and render methods, but it does not guarantee that the full child component will be resolved because it passively inherits from WrappedComponent. All calls are reversed. It has two features, rendering hijack and controlling state.

    Rendering hijacked

    We can read, add, modify, and delete props in the output of any React element, or read or modify the React element tree, or conditionally display the element tree, or wrap the element tree with style controls.

    // Conditional render
    const MyContainer=(WrappedComponent) = >
      class extends WrappedComponent{
          render(){
              if(this.props.loggedIn){
                  return super.render();
              }
              else{
                  return null; }}}Copy the code
    // Modify the output of render
    const MyContainer=(WrappedComponent) = >
       class extends wrappedComponent{
           render{
               const elementsTree=super.render();
               let newProps={};
               
               if (elementsTree && elementsTree.type==='input'){
                   newProps={value:'may the force be with you'};
               }
               const props=Object.assign({},elementsTree.props,newProps);
               const newElementsTree =React.cloneElement(
               elementsTree,props,elementsTree.props.children);
               returnnewElementsTree; }}Copy the code

    As you can see, the value of the top-level input component is rewritten to ‘May the force be with you’. So, we can do all sorts of things, even reverse the tree of elements, or change the props in the tree of elements, which is how the Radium library is constructed.

    Control of the state

    Higher-order components can read, modify, or remove state from a WrappedComponent instance, and can add state if necessary, but doing so would confuse component state. Most higher-order components should restrict reading or adding state.

    const MyContainer=(WrappedComponent) = >
      class extends WrappedComponent{
       render(){
         return(
         <div>
           <h2>HOC  Debuger Component</h2>
           <p>Props</p> <pre>{JSON.stringfy(this.props,null,2)}</pre>
           <p>state</p><pre>{JSON.stringfy(this.state,null,2)}</pre>
           {super.render()}
         </div>); }}Copy the code