React-props (1) React-props (2) React-props (2)

Using Hooks for some time, in conjunction with several documents, to sort out some notes 📒

Before we get to react-hooks, let’s get to know how to reuse react state logic. This will help you understand hooks

React components are the basic unit of code reuse, and the composite-based component reuse mechanism is quite elegant. However, it is not so easy to reuse more fine-grained logic (state logic, behavior logic, etc.), so we solve our state logic reuse problem by analyzing the following patterns

Mixin

Mixin design patterns

Mixin patterns are classes that provide functionality that can be simply inherited by one or a group of subclasses, with the intent to reuse the functionality


Many open source libraries provide
The realization of the Mixin, such as
Underscore. Extend method

Implement the Minxin method by hand

const mixin = function (target, mixins{

    const newObj = target;

    newObj.prototype = Object.create(target.prototype);



    for (let prop in mixins) {

        if (mixins.hasOwnProperty(prop)) {

            newObj.prototype[prop] = mixins[prop];

        }

    }



    return newObj;

}



const Person = {

    run(a)= > {

        console.log('I can run');

    }

};



const Son = function ({

    console.log('hello world');

};



const User = mixin(Son, Person);



const vnues = new User(); // 'hello world'

vnues.run(); // 'I can run'

Copy the code

❗️ you can see that the mixin method can be used to extend any method of any object to the target object, that is to say, the mixin method can reuse state logic, behavior logic, etc

The React of the Mixin

The Mixin scheme was born out of OOP intuition. React itself was somewhat functional, but in the early days only the React.createclass () API was provided to define components for users:

/ / define a Mixin

var Mixin1 = {

  getMessagefunction({

    return 'hello react';

  }

};

var Mixin2 = {

  componentDidMountfunction({

    console.log('Mixin2.componentDidMount()');

  }

};



// Enhance existing components with mixins

var MyComponent = React.createClass({

  mixins: [Mixin1, Mixin2],

  renderfunction({

    return <div>{this.getMessage()}</div>;

  }

});

Copy the code

The pitfalls of mixins

Mixins introduce ‘Mixins introduce implicit dependencies

You might write a stateful component, and then your colleague might add a mixin that reads the state of that component. After a few months, you may want to move the state to the parent component so that it can be shared with its siblings. Would you remember to update the mixin to read props instead of state? What if, at this point, other components are also using this mixin?

var RowMixin = {

  renderHeaderfunction({

    return (

      <div className='row-header'>

        <h1>

{this.getheaderText ()} // Implicit dependency

        </h1>

      </div>


    );

  }

};



var UserRow = React.createClass({

  mixins: [RowMixin], // Mix in the renderHeader method

  getHeaderText: function({

    return this.props.user.fullName;

  },

  renderfunction({

    return (

      <div>

        {this.renderHeader()}

        <h2>{this.props.user.biography}</h2>

      </div>


    )

  }

});

Copy the code
Implicit dependencies lead to opaque dependencies and rapidly rising maintenance and understanding costs:
  • It is difficult to quickly understand component behavior and requires a comprehensive understanding of all mixin-dependent extension behavior and their interactions

  • The group value method and state field are not easy to delete, because it is difficult to determine whether mixins depend on it

  • Mixins are also difficult to maintain because Mixin logic is eventually flattened and merged together, making it difficult to figure out the inputs and outputs of a Mixin

Mixins caused name clashes

You defined getDefaultProps in that Mixin, and another Mixin defined the same name getDefaultProps, causing a conflict.

var DefaultNameMixin = {

   // Name conflict

    getDefaultProps: function ({

        return {name"Lizie"};

    }

};



var DefaultFoodMixin = {

  // Name conflict

    getDefaultProps: function ({

        return {food"Pancakes"};

    }

};



var ComponentTwo = React.createClass({

    mixins: [DefaultNameMixin, DefaultFoodMixin],

    renderfunction ({

        return (

            <div>

                <h4>{this.props.name}</h4>

                <p>Favorite food: {this.props.food}</p>

            </div>


        );

    }

});

Copy the code

Mixins cause snowballing complexity

Even if mixins are simple to start with, they tend to get complicated over time

Mixin ‘tends to add more states’, which reduces The predictability of The application (‘ The more state in your application, The harder it is to reason about it.’), This leads to a huge increase in complexity, but we really want to try to simplify the state a little bit.

// someMixin

const someMixin = { 

  // Add 'newNumber state' to component

  // If you use the createReactClass() method to create a component, you need to provide a separate getInitialState method that returns the initial state:

  getInitialState() {

    return {

      newNumber1

    }

  },

  setNewNumber(num) {

    this.setState({

      newNumber: num

    })

  }

}

// someOtherMixin

const someOtherMixin = {  

  someMethod(number) {

    console.log(number)

  }

}

const Composer = React.createClass({

  mixins: [

    someMixin,

    someOtherMixin

].

  render() {

    return <p>{this.state.newNumber}</p>

  },

  someStaticMethod(num) {

    this.setNewNumber(num)

    this.someMethod(num)

  }

})

class App extends React.Component {  

  constructor(props) {

    super(props)

    this.callChildMethod = this.callChildMethod.bind(this)

  }

  callChildMethod() {

    this.refs.composer.someStaticMethod(5)

  }

  render() {

    return (

      <div>

        <button onClick={this.callChildMethod}></button>

        <Composer ref="composer" />

      </div>

    )

  }

}

Copy the code

So React V0.13.0 ditches Mixin in favor of HOC, and ❗️ embraces ES6, whose classes support inheritance but not mixins

HOC high-level components

HOC is an advanced technique used in React to reuse component logic. HOC itself is not part of the React API; it is a design pattern based on the composite features of React.

High-order components can be seen as an implementation of the Decorator pattern with React. Specifically, high-order components are functions that take components as arguments and return new components.

export default (WrappedComponent) => {

  class NewComponent extends Component {

    // You can do a lot of custom logic

    render () {

      return <WrappedComponent />

    }

  }

  return NewComponent

}

Copy the code

Decorator pattern

The decorator pattern is a design pattern that dynamically assigns responsibility to a class or object. It can add responsibilities dynamically during the running of a program without changing the class or object itself.

ES7 provides a similar syntactic sugar decorator for Java annotations to implement the Decorator pattern. Very simple to use:

@testable

class MyTestableClass {

  // ...

}



function testable(target{

  target.isTestable = true;

}



MyTestableClass.isTestable // true

Copy the code

HOC factory implementation

The property broker

This method is a combination in nature. It is probably the first implementation method that comes to our mind. The general implementation framework is as follows:

function proxyHOC(WrappedComponent{

  return class PP extends React.Component {

    render() {

      return <WrappedComponent {. this.props} / >

    }

  }

}

Copy the code

Reverse inheritance

Inheritance Inversion is HOC to Inheritance WrappedComponent. Now that we have this component, we can decorate and modify it from the inside, starting with its implementation framework:

function inheritHOC(WrappedComponent{

  return class extends WrappedComponent {

    render() {

      return super.render();

    }

  }

}

Copy the code

What can HOC do ❓

Operates on all incoming props

You can read, add, edit, and delete props(properties) passed to WrappedComponent.

Be careful when deleting or editing important props(properties). You should use the namespace to ensure that higher-order components’ props don’t break the WrappedComponent.

// Example: Add new props(properties). The currently logged user in the application can be accessed in WrappedComponent via this.props. User.

function proxyHOC(WrappedComponent{

  return class PP extends React.Component {

    render() {

      const newProps = {

        user: currentLoggedInUser

      }

      return <WrappedComponent {. this.props} {. newProps} / >

    }

  }

}

Copy the code

Access component instances through refs

function refsHOC(WrappedComponent{

  return class RefsHOC extends React.Component {

    proc(wrappedComponentInstance) {

      wrappedComponentInstance.method()

    }



    render() {

      const props = Object.assign({}, this.props, {refthis.proc.bind(this)})

      return <WrappedComponent {. props} / >

    }

  }

}

Copy the code

The Ref callback is executed when the WrappedComponent renders, and you get a reference to the WrappedComponent. This can be used to read/add props to an instance and call its methods.

In proc, wrappedComponentInstance is null if WrappedComponent is a stateless component, because stateless components do not have this and do not support ref.

To extract the state

You can extract state by passing in props and callback functions,

function proxyHOC(WrappedComponent{

  return class PP extends React.Component {

    constructor(props) {

      super(props)

      this.state = {

        name' '

      }



      this.onNameChange = this.onNameChange.bind(this)

    }

    onNameChange(event) {

      this.setState({

        name: event.target.value

      })

    }

    render() {

      const newProps = {

        name: {

          valuethis.state.name,

          onChangethis.onNameChange

        }

      }

      return <WrappedComponent {. this.props} {. newProps} / >

    }

  }

}

Copy the code

When used:

@proxyHOC

class Test extends React.Component {

    render () {

        return (

            <input name="name" {. this.props.name} / >

        );

    }

}



export default proxyHOC(Test);

Copy the code

In this way, it is possible to convert the input into a controlled component.

Wrap the WrappedComponent with other elements

The easy way to understand this is to wrap the WrappedComponent around the desired nested structure

function proxyHOC(WrappedComponent{

  return class PP extends React.Component {

    render() {

      return (

        <div style={{display: 'block'}} >

          <WrappedComponent {. this.props} / >

        </div>

      )

    }

  }

}

Copy the code

Render Highjacking

It’s called Render Highjacking because HOC controls the Render output of the WrappedComponent and can do all sorts of things with it.

In render hijacking, you can: state(state), props(properties)

  • Read, add, edit, and delete props in any React element render output
  • Read and modify the React element tree of the Render output
  • Conditionally render the element tree
  • Wrap styles in the element tree (as in the Props Proxy)
function inheritHOC(WrappedComponent{

  return class Enhancer 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)

      return newElementsTree

    }

  }

}

Copy the code

How to use HOC gracefully

Combination compose

 function compose(. funcs{

  return funcs.reduce((a, b) = >(... args) => a(b(... args)))

}

Copy the code

usage

compose(fn1, fn2, fn3) (... Args is equivalent to FN1 (FN2 (FN3 (... args)))

Copy the code

A decorator

@proxyHOC

class Test extends React.Component {

    render () {

        return (

            <input name="name" {. this.props.name} / >

        );

    }

}

Copy the code

Considerations for using HOC

⚠️ Do not use HOC in render methods

The React diff algorithm uses the component identity to determine whether it should update an existing subtree or discard it and mount a new one. If the component returned from Render is the same as the component in the previous render (===), React updates the subtree recursively by differentiating it from the new one. If they are not equal, the previous subtree is completely unloaded.

render() {

  // Each call to the Render function creates a new EnhancedComponent

  // EnhancedComponent1 ! == EnhancedComponent2

  const EnhancedComponent = enhance(MyComponent);

  // This will cause subtrees to be unmounted and remounted every time they are rendered!

  return <EnhancedComponent />;

}

Copy the code

This isn’t just a performance issue – remounting a component causes a loss of state for that component and all its children.

If you create HOC outside of the component, then the component will only be created once. Therefore, the same component is rendered each time. Generally speaking, this is in line with your expected performance.

⚠️ Be sure to copy static methods

Sometimes it’s useful to define static methods on the React component. For example, the Relay container exposes a static method getFragment to facilitate composing GraphQL fragments.

However, when you apply HOC to a component, the original component will be wrapped with a container component. This means that the new component does not have any static methods of the original component.

// Define a static function

WrappedComponent.staticMethod = function({/ *... * /}

// Now use HOC

const EnhancedComponent = enhance(WrappedComponent);



// There is no staticMethod for the enhanced component

typeof EnhancedComponent.staticMethod === 'undefined' // true

Copy the code

To solve this problem, you can copy these methods to the container component before returning:

function enhance(WrappedComponent{

  class Enhance extends React.Component {/ *... * /}

  // must know exactly which methods to copy :(

  Enhance.staticMethod = WrappedComponent.staticMethod;

  return Enhance;

}

Copy the code

⚠️Refs will not be passed

Although the convention for higher-order components is to pass all props to the wrapped component, this does not apply to refs. That’s because a REF isn’t really a prop – like a key, it’s handled specifically by React. If you add ref to HOC’s return component, the REF reference points to the container component, not the wrapped component.

Pass Refs using the Forwarding Refs API

Ref Forwarding: Is a technique for automatically passing the Ref hook to a component’s descendants

Consider a FancyButton component that renders a native Button DOM element:

function FancyButton(props{

  return (

    <button className="FancyButton">

      {props.children}

    </button>


  );

}



 const ref = React.createRef();

<FancyButton ref={ref}>Click me!</FancyButton>;

Copy the code

The code above, the FancyButton component, renders an HTML element. React hides the process of rendering code into page elements for users. When other components use FancyButton, there is no direct way to obtain FancyButton elements. This design method facilitates component fragmentation management and reduces coupling.

But components like FancyButton simply encapsulate basic HTML elements. In some cases, upper-layer components may prefer to treat it as a basic HTML element, and some effects require direct manipulation of the DOM, such as the Focus, Selection, and animations effects.

In the following example, FancyButton uses the React. ForwardRef to get the ref passed to it and forward it to the DOM button it renders:

const FancyButton = React.forwardRef((props, ref) = > (

  <button ref={ref} className="FancyButton">

    {props.children}

  </button>


));



 const ref = React.createRef();

<FancyButton ref={ref}>Click me!</FancyButton>;

Copy the code

Convention: Do not change the original component

Don’t try to modify the component prototype (or otherwise change it) in HOC.

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

There are some bad consequences. One is that input components can no longer be used in the way they were before HOC was enhanced. Worse, if you augment it with another HOC that also modifies componentDidUpdate, the previous HOC will fail! At the same time, HOC cannot be applied to functional components that have no life cycle.

Convention: Pass unrelated props to the wrapped component

HOC adds features to components. They should not change their agreements drastically. The component returned by HOC should maintain a similar interface to the original component.

HOC should pass through props that have nothing to do with itself. Most HOC should include a render method like the one below

render() {

  // Higher-order components only need visible

  const{ visible, ... props } =this.props;

  // Pass props to the wrapped component

  return (

    <WrappedComponent

     {. props}

    />


  );

}

Copy the code

Convention: Wrap the display name for easy debugging

Container components created by HOC will show up in React Developer Tools just like any other component. To facilitate debugging, select a display name to indicate that it is a product of HOC.

❗️ To facilitate debugging, we can manually specify a displayName for HOC. HOCName(WrappedComponentName) is officially recommended:

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

HOC defects

  • Scalability limitations: HOC is not a complete replacement for Mixin

  • Ref transfer problem: Ref is cut off and needs to be dealt with specially in development

  • Wrapper Hell: HOC flooding, Wrapper Hell appears

Render Props

As mentioned earlier, components are the basic unit of code reuse in React, and the composite-based component reuse mechanism is quite elegant. For more fine-grained logic (state logic, behavior logic, etc.), it is not easy to reuse logic. The mode for reuse logic is to process reuse logic into component forms, including the render props. Schemes such as Mixin, Render Props, HOC, etc. are all supermodels explored within the rules of an existing game (component mechanics)

Create a render props

The component doesn’t define its own render props. Instead, it passes in an externally defined render function called render props(children props if the name is children)

// render props

const Test = props= > props.render('hello world')

const App = (a)= > (

    <Test

{/ * *

With render properties (Render Props) component requires a returnReactElement and call its functions instead of implementing your own rendering logic.

/ * *}

      render={text= >
 <div>{text}</div>}

    />

)



ReactDOM.render((<App />, root) // Returns<div>hello world</div>

Copy the code

Technically, both of them are based on the component combination mechanism. Render Props have the same extensibility capability as HOC. The reason why it is called Render Props is not to say that it can only be used to reuse rendering logic, but to indicate that in this mode, components are combined by Render (). Similar to HOC mode with render() of the Wrapper, the two are very similar in form and both produce a layer of “Wrapper” (EComponent and RP).

/ / HOC definitions

const HOC = Component= > WrappedComponent;

/ / HOC use

const Component;

const EComponent = HOC(Component);

<EComponent />



// Render Props definition

const RP = ComponentWithSpecialProps;

// Render Props use

const Component;

<RP specialRender={() => <Component />} />

Copy the code

More interestingly, Render Props and HOC can even be converted to each other (they can coexist, not be mutually exclusive) :

function RP2HOC(RP{

  return Component= > {

    return class extends React.Component {

      static displayName = "RP2HOC";

      render() {

        return (

          <RP

            specialRender={renderOptions => (

<Component {... this.props} renderOptions={renderOptions} />

            )}

          />

        )

      }

    }

  }

}



function HOC2RP(HOC) {

  const RP = class extends React.Component {

    static displayName = "HOC2RP";

    render() {

      return this.props.specialRender();

    }

  }

  return HOC(RP);

}



class Hello extends React.Component {

  render() {

    return <div>Hello {this.props.name}</div>;

  }

}



class RP extends React.Component {

  render() {

    return (

      <div style={{ background: "#2b80b6" }}>{this.props.specialRender()}</div>

    );

  }

}



// RP -> HOC

const HOC = RP2HOC(RP)

const EComponent = HOC(Hello);

ReactDOM.render(

  <EComponent name="RP -> HOC" />,

  document.getElementById("container1")

);



// HOC -> RP

const NewRP = HOC2RP(HOC)

ReactDOM.render(

  <NewRP specialRender={() => <Hello name="HOC -> RP" />} />,

  document.getElementById("container2")

);

Copy the code

Render Props defects

  • Cumbersome to use: HOC can be reused using only one line of code with the help of decorator syntax, which Render Props cannot

  • Deep nesting: Render Props got rid of the problem of multi-layer nesting of components, but converted to nesting of function callbacks

The last