As any project grows to a certain level of complexity, it will inevitably face the problem of logic reuse. Logic reuse in React is typically implemented in the following ways: mixins, HOC, decorators, Render Props, and hooks. This article mainly analyzes the advantages and disadvantages of the above methods to help developers make more suitable methods for business scenarios.

Mixin

This is probably the first thing developers who have just moved from Vue to React can think of. Mixins have been widely used in various object-oriented languages to create an effect similar to multiple inheritance for single-inheritance languages. Although deprecated by React, mixins were a design pattern for code sharing implemented by React.

The generalized mixin method is to attach all the methods in the mixin Object to the original Object by assigning values to realize the mixing of objects, similar to the function of object.assign () in ES6. The principle is as follows:

const mixin = function (obj, mixins) {
  const newObj = obj
  newObj.prototype = Object.create(obj.prototype)

  for (let prop in mixins) {
    // Iterate over mixins properties
    if (mixins.hasOwnPrototype(prop)) {
      // Check whether it is mixin's own property
      newObj.prototype[prop] = mixins[prop]; / / assignment}}return newObj
};
Copy the code

Use mixins in React

Assuming that in our project, multiple components need to set the default name property, using mixin saves us from having to write the same getDefaultProps method in different components. We can define a mixin:

const DefaultNameMixin = {
  getDefaultProps: function () {
    return {
      name: "Joy"}}}Copy the code

To use mixins, we add mixins properties to the component, and then wrap our mixins in an array as mixins property values:

const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin]
  render: function () {
    return <h2>Hello {this.props.name}</h2>}})Copy the code

Write goodmixinIt can be reused in other components.

Since the mixins attribute value is an array, it means that we can call multiple mixins from the same component. With a slight change in the above example:

const DefaultFriendMixin = {
  getDefaultProps: function () {
    return {
      friend: "Yummy"}}}const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin, DefaultFriendMixin]
  render: function () {
    return (
      <div>
        <h2>Hello {this.props.name}</h2>
        <h2>This is my friend {this.props.friend}</h2>
      </div>)}})Copy the code

We can even include other mixins in one mixin.

For example, write a new mixin ‘ ‘DefaultProps’ containing DefaultNameMixin and DefaultFriendMixin above:

const DefaultPropsMixin = {
  mixins: [DefaultNameMixin, DefaultFriendMixin]
}

const ComponentOne = React.createClass({
  mixins: [DefaultPropsMixin]
  render: function () {
    return (
      <div>
        <h2>Hello {this.props.name}</h2>
        <h2>This is my friend {this.props.friend}</h2>
      </div>)}})Copy the code

So far, we can conclude that mixins have at least the following advantages:

  • You can use the same mixin in multiple components;
  • Multiple mixins can be used within the same component;
  • Multiple mixins can be nested within the same mixin;

But an advantage can turn into a disadvantage in different scenarios:

  • If the encapsulation of the original component is broken, it may be necessary to maintain the new state and props.
  • The naming of different mixins is unknowable and very prone to conflict;
  • Recursive call problems may occur, increasing project complexity and maintenance difficulty;

In addition, mixins have their own logic for handling state conflicts, method conflicts, and the order in which multiple lifecycle methods are called. Interested students can refer to the following articles:

  • Use of React Mixin
  • Mixins Considered Harmful

High order component

React strips mixins and replaces them with higher-order components because mixins have these flaws.

A higher-order component is essentially a function that takes a component as an argument and returns a new component.

React implements some common components, such as withRouter in React-Router and Connect in Redux. Take The case of withRouter.

By default, components rendered by Route matching must have this.props, Route parameters, and functional navigation to execute this.props.history.push(‘/next’) to the page of the Route. The withRouter function of the high-order component isto wrap a component that is not wrapped by the Route Route into the Route, so that the three objects history, location, and match of the React-Router are added to the props property of the component, so functional navigation jump can be realized.

WithRouter’s implementation principle:

const withRouter = (Component) = > {
  const displayName = `withRouter(${Component.displayName || Component.name}) `
  const C = props= > {
    const{ wrappedComponentRef, ... remainingProps } = propsreturn (
      <RouterContext.Consumer>
        {context => {
          invariant(
            context,
            `You should not use <${displayName} /> outside a <Router>`); return ( 
       ) }}  ) }Copy the code

Use code:

import React, { Component } from "react"
import { withRouter } from "react-router"
class TopHeader extends Component {
  render() {
    return (
      <div>Navigation {/* Click jump login */}<button onClick={this.exit}>exit</button>
      </div>
    )
  }

  exit = () = > {
    // After wrapping the withRouter higher-order function, you can use this.props to jump
    this.props.history.push("/login")}}// Wrap the component with withRouter, return history,location, etc
export default withRouter(TopHeader)
Copy the code

Since the essence of higher-order components is a way of getting components and returning new ones, it could theoretically be multiple nesting just like mixins.

Such as:

Write a higher-order function that enables singing

import React, { Component } from 'react'

const widthSinging = WrappedComponent= > {
	return class HOC extends Component {
		constructor () {
			super(... arguments)this.singing = this.singing.bind(this)
		}

		singing = () = > {
			console.log('i am singing! ')}render() {
			return <WrappedComponent />}}}Copy the code

Write a higher-order function that enables dancing

import React, { Component } from 'react'

const widthDancing = WrappedComponent= > {
	return class HOC extends Component {
		constructor () {
			super(... arguments)this.dancing = this.dancing.bind(this)
		}

		dancing = () = > {
			console.log('i am dancing! ')}render() {
			return <WrappedComponent />}}}Copy the code

Use the higher-level components above

import React, { Component } from "react"
import { widthSing, widthDancing } from "hocs"

class Joy extends Component {
  render() {
    return <div>Joy</div>}}// Joy can sing and dance
export default widthSinging(withDancing(Joy))
Copy the code

As can be seen from the above, simply using higher-order functions for a simple package, you can turn the original simple Joy into a nightclub prince who can both sing and dance!

A convention to use HOC

When using HOC, there are a few conventional conventions:

  • Pass unrelated Props to the wrapper component (pass Props unrelated to its specific content);
  • Step composition (avoid different forms of HOC series calls);
  • Include the displayName of the display for easy debugging (each HOC should conform to the regular displayName);
  • Don’t inrenderUse high-order components in functions (each render, high-order returns a new component, affecting diff performance);
  • Static methods must be copied (the new component returned with a higher order does not contain the static methods of the original component);
  • Avoid using ref (ref is not passed);

Pros and cons of HOC

At this point, we can summarize the advantages of HOC:

  • HOCIs a pure function, easy to use and maintenance;
  • Due to the sameHOCIs a pure function that supports passing in multiple parameters to enhance its range of application;
  • HOCReturns a component, can be composed nested, flexibility;

Of course, there are some problems with HOC:

  • When more than oneHOCWhen nested, the child component cannot be identified directlypropsFrom whichHOCResponsible for delivery;
  • When the parent and child components have the same namepropsCauses the parent component to overwrite the child component with the same namepropsAndreactNo error reporting, low developer perception;
  • eachHOCAll return a new component, resulting in a lot of useless components, while deepening the component hierarchy, not easy to troubleshoot problems;

Modifiers and higher-order components belong to the same pattern and are not discussed here.

Render Props

Render Props is a very flexible and highly reusable mode, which can encapsulate a specific behavior or function into a component and provide it to other components so that other components can have such capabilities.

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

React Props () React Props () Render Props is a technology for code sharing between React Components. The component Props contains a function-type property that the component can call to implement its internal rendering logic.

Official example:

<DataProvider render={(data) = > <h1>Hello {data.target}</h1>} / >Copy the code

As shown above, the DataProvider component has a props property called Render (or something else), which is a function that returns a React Element that is called inside the component to render. So this component uses the Render props technology.

The reader may be wondering, “Why do we need to call the props property to render inside the component instead of rendering directly inside the component?” Render props isn’t a skill every React developer needs to master, and you probably never need to use it, but it does give developers an alternative to thinking about component code sharing.

Render PropsUsage scenarios

We may need to use popovers frequently in project development. The popover UI can be varied, but the functions are similar, namely opening and closing. Take ANTD as an example:

import { Modal, Button } from "antd"
class App extends React.Component {
  state = { visible: false }

  // Control the popup display to hide
  toggleModal = (visible) = > {
    this.setState({ visible })
  };

  handleOk = (e) = > {
    // Do something about it
    this.setState({ visible: false})}render() {
    const { visible } = this.state
    return (
      <div>
        <Button onClick={this.toggleModal.bind(this, true)} >Open</Button>
        <Modal
          title="Basic Modal"
          visible={visible}
          onOk={this.handleOk}
          onCancel={this.toggleModal.bind(this, false)}
        >
          <p>Some contents...</p>
        </Modal>
      </div>)}}Copy the code

The above is the simplest Model use example, even if simple use, we still need to pay attention to its display state, implementation of its switch method. But developers really only want to focus on onOk related to business logic, and ideally use it like this:

<MyModal>
  <Button>Open</Button>
  <Modal title="Basic Modal" onOk={this.handleOk}>
    <p>Some contents...</p>
  </Modal>
</MyModal>
Copy the code

This can be done using render props:

import { Modal, Button } from "antd"
class MyModal extends React.Component {
  state = { on: false }

  toggle = () = > {
    this.setState({
      on:!this.state.on
    })
  }

  renderButton = (props) = > <Button {. props} onClick={this.toggle} />

  renderModal = ({ onOK, ... rest }) = > (
    <Modal
      {. rest}
      visible={this.state.on}
      onOk={()= > {
        onOK && onOK()
        this.toggle()
      }}
      onCancel={this.toggle}
    />
  )

  render() {
    return this.props.children({
      Button: this.renderButton,
      Modal: this.renderModal
    })
  }
}
Copy the code

So we have Modal with state and basic functionality, and we only need to focus on specific business logic when we use Modal on other pages.

Render props is a real React component, not just a function that can return components like HOC. This means that using Render props does not generate component-level nesting problems like HOC, and there is no need to worry about overwriting problems caused by naming conflicts.

render propsUse restrictions

Avoid using arrow functions in render props because of the performance impact.

Such as:

// Bad example
class MouseTracker extends React.Component {
  render() {
    return (
      <Mouse render={mouse= > (
        <Cat mouse={mouse} />)} / >)}}Copy the code

This is not good because the render method can be rendered multiple times, and using the arrow function will result in a different value being passed to render each time, when there is no actual difference, which can cause performance problems.

It would be better to define the function passed into render as an instance method, so that even if we render multiple times, the binding is always the same function.

// Good example
class MouseTracker extends React.Component {
  renderCat(mouse) {
  	return <Cat mouse={mouse} />
  }

  render() {
    return (
		  <Mouse render={this.renderTheCat} />)}}Copy the code

render propsThe advantages and disadvantages of

  • advantages

    • The props name can be modified without overwriting each other;
    • Know the source of props;
    • There is no multi-layer nesting of components;
  • disadvantages

    • Cumbersome writing;

    • Unable to access data outside the return statement;

    • Easy to create function callback nesting;

      The following code:

      const MyComponent = () = > {
        return (
          <Mouse>
            {({ x, y }) => (
              <Page>
                {({ x: pageX, y: pageY }) => (
                  <Connection>
                    {({ api }) => {
                      // yikes
                    }}
                  </Connection>
                )}
              </Page>
            )}
          </Mouse>)}Copy the code

Hook

React is all about components at its core, so React has been working on optimizing and improving the way components are declared. From the earliest class components to functional components, there are pros and cons. The class component can provide us with a complete life cycle and state, but it is very cumbersome to write, while the function component is very simple and convenient to write, but its limitation is that it must be a pure function, cannot contain the state, and does not support the life cycle, so the class component cannot replace the function component.

The React team decided that components were best written as functions, not classes, which led to React Hooks.

React Hooks are designed to enhance function components so that they can write a fully functional component without using “classes” at all.

React class components are unwieldy.

import React, { Component } from "react"

export default class Button extends Component {
  constructor() {
    super(a)this.state = { buttonText: "Click me, please" }
    this.handleClick = this.handleClick.bind(this)}handleClick() {
    this.setState(() = > {
      return { buttonText: "Thanks, been clicked!"}})}render() {
    const { buttonText } = this.state
    return <button onClick={this.handleClick}>{buttonText}</button>}}Copy the code

The above is a simple button component that contains the basic state and click method, and the state changes when the button is clicked.

This is a very simple functional component, but it requires a lot of code to implement. Since function components do not contain state, we cannot use function components to declare a component that does this. But we can use Hook to do this:

import React, { useState } from "react"

export default function Button() {
  const [buttonText, setButtonText] = useState("Click me, please")

  function handleClick() {
    return setButtonText("Thanks, been clicked!")}return <button onClick={handleClick}>{buttonText}</button>
}
Copy the code

Hooks are lightweight in comparison, retaining their state while remaining close to functional components.

In this example, the first hook useState() is introduced. In addition, React provides useEffect(), useContext(), useReducer(), and other hooks. See the official hooks and their usage.

Hooks are also flexible because, in addition to the official base hooks, they can be used to encapsulate and customize hooks, making it easier to reuse code.

Hook the pros and cons

  • advantages
    • Easier to reuse code;
    • Clean code style;
    • Less code;
  • disadvantages
    • State is out of sync (functions run independently, each function has a separate scope)
    • More rational use is neededuseEffect
    • Small granularity, complex logic needs to be abstracted out a lothook

conclusion

There is no optimal solution for higher-order components, Render props, react Hooks, other than mixins being a bit behind due to their obvious flaws. They are all pros and cons. Even the most popular React Hook, although each hook looks very brief and clean, in actual business, one business function usually corresponds to multiple hooks, which means that when the business changes, it is necessary to maintain the changes of multiple hooks. Compared with maintaining a class, The mental burden may increase a lot. The best solution is one that fits your business.


Reference Documents:

  • Use of React Mixin
  • Mixins Considered Harmful
  • Higher-Order Components
  • Render Props
  • React Props and its use scenarios
  • Hook profile