React code sharing best practices

Time:2022-8-4

When any project develops to a certain complexity, it will inevitably face the problem of logic reuse. stayReactThere are usually the following ways to realize logical reuse in:MixinHigh order component (HOC)DecoratorRender PropsHook。 This article mainly analyzes the advantages and disadvantages of the above methods to help developers make more suitable methods for business scenarios.

Mixin

This may be just fromVueto turn toReactThe first method that developers can think of.MixinIt has been widely used in various object-oriented languages,Its function is to create an effect similar to multiple inheritance for single inheritance languages。 Although nowReactIt has been abandoned, butMixinIt wasReactA design pattern for code sharing.

The generalized mixin method is to mount the methods in the mixin object to the original object by means of assignment 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) {
    //Traverse the properties of mixins
    if (mixins.hasOwnPrototype(prop)) {
      //Determine whether it is the self attribute of mixin
      newObj. prototype[prop] = mixins[prop]; //  assignment
    }
  }
  return newObj
};

Using mixin in react

Suppose that in our project, multiple components need to set the defaultnameProperties, usingmixinSo that we don’t have to write multiple identical ones in different componentsgetDefaultPropsMethod, we can define amixin

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

In order to usemixin, which needs to be added in the componentmixinsAttribute, and then write usmixinWrap it into an array asmixinsAttribute value of:

const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin]
  render: function () {
    return Hello {this.props.name}
  }
})

WrittenmixinIt can be reused in other components.

becausemixinsThe attribute value is an array, which means that weMultiple calls can be made in the same componentmixin。 In the above example, a slight change is made to get:

const DefaultFriendMixin = {
  getDefaultProps: function () {
    return {
      friend: "Yummy"
    }
  }
}

const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin, DefaultFriendMixin]
  render: function () {
    return (
      
        Hello {this.props.name}
        This is my friend {this.props.friend}
      
    )
  }
})

We can even be in amixinContains othersmixin

For example, write a new onemixin``DefaultPropsIncluding the aboveDefaultNameMixinandDefaultFriendMixin

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

const ComponentOne = React.createClass({
  mixins: [DefaultPropsMixin]
  render: function () {
    return (
      
        Hello {this.props.name}
        This is my friend {this.props.friend}
      
    )
  }
})

So far, we can conclude thatmixinAt least have the following advantages:

  • The same can be used in multiple componentsmixin
  • Multiple can be used in the same componentmixin
  • Can be in the samemixinNested multiplemixin

However, in different scenarios, advantages may also become disadvantages:

  • Destroying the encapsulation of the original components may require maintenance of new onesstateandpropsEqual state
  • DifferentmixinThe name in is unknowable, which is very easy to conflict
  • Recursive call may occur, which increases the complexity of the project and the difficulty of maintenance

besides,mixinIt has its own processing logic for state conflicts, method conflicts, and the call sequence of multiple lifecycle methods. Interested students can refer to the following articles:

High order components

becausemixinThere are the above defects, soReactStrippedmixin, use insteadHigh order componentsTo replace it.

High order componentsIt is essentially a function, which takes a component as a parameter and returns a new component

ReactThe official also uses it when implementing some public componentsHigh order components, such asreact-routerMediumwithRouter, andReduxMediumconnect。 Here withwithRouterAs an example.

By default, it must be throughRouteOnly components with route matching rendering existthis.props, to ownRouting parameters, can be usedFunctional navigationImplementation of the writing method ofthis.props.history.push('/next')Jump to the page of the corresponding route.High order componentsMediumwithRouterThe function is to make aRouteComponents for routing packages, packages toRouteInside, so thatreact-routerThree objects ofhistorylocationmatchPut into thepropsAttribute, so it can be implementedFunctional navigation jump

withRouterImplementation principle of:

const withRouter = (Component) => {
  const displayName = `withRouter(${Component.displayName || Component.name})`
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props
    return (
      
        {context => {
          invariant(
            context,
            `You should not use  outside a `
          );
          return (
            
          )
        }}
      
    )
}

Use code:

import React, { Component } from "react"
import { withRouter } from "react-router"
class TopHeader extends Component {
  render() {
    return (
      
        navigation bar
        {/ * click jump login * /}
        sign out
      
    )
  }

  exit = () => {
    //After wrapping withrouter higher-order functions, you can use this.props to jump
    this.props.history.push("/login")
  }
}
//Use withrouter to package components and return history, location, etc
export default withRouter(TopHeader)

becauseHigh order componentsThe essence of isMethod to get the component and return the new componentSo in theory, it can also be likemixinAlso realize multiple nesting.

For example:

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 
		}
	}
}

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 
		}
	}
}

Use the above high-order components

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

class Joy extends Component {
  render() {
    return Joy
  }
}

//Give joy the ability to sing and dance
export default widthSinging(withDancing(Joy))

It can be seen from the above that by simply wrapping with high-order functions, you can turn the original simple joy into a nightclub prince who can both sing and dance!

Conventions for using hoc

in useHOCAt the time of, there were some conventional agreements:

  • Pass irrelevant props to the packaging component (pass props irrelevant to its specific content);
  • Step by step combination (avoid different forms of HOC serial calls);
  • Include the displayName of the display for debugging (each hoc should comply with the display name of the rule);
  • Don’t berenderUse high-order components in the function (each render, the high-order will return new components, which will affect the diff performance);
  • Static methods must be copied (new components returned through high-order will not contain static methods of the original components);
  • Avoid using ref (Ref will not be passed);

Advantages and disadvantages of hoc

So far, we can summarizeHigh order component (HOC)Advantages:

  • HOCIs a pure function, easy to use and maintain;
  • Also due toHOCIs a pure function, which supports the introduction of multiple parameters to enhance its scope of application;
  • HOCWhat is returned is a component, which can be combined and nested, with strong flexibility;

of courseHOCThere will also be some problems:

  • When multipleHOCWhen nested, you cannot directly judge thepropsFrom whichHOCResponsible for delivery;
  • When a parent-child component has the same nameprops, which will cause the parent component to cover the component with the same namepropsAndreactNo error will be reported, and the developer’s perception is low;
  • every lastHOCAll return a new component, resulting in a lot of useless components. At the same time, it deepens the component level, which is not convenient for troubleshooting problems;

DecoratorandHigh order componentsIt belongs to the same model and will not be discussed here.

Render Props

Render PropsIt is a very flexible and highly reusable mode, which can encapsulate specific behaviors or functions into a component and provide it to other components for use, so that other components 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.

This isReactOfficial forRender PropsThe definition of is translated into vernacular, namely:“Render PropsIs to achieveReact ComponentsA technology of code sharing between componentspropsIt contains afunctionType, which can be called by the componentpropsProperty to implement the internal rendering logic of the component “.

Official example:

Hello {data.target}} />

As above,DataProviderThe component has arender(or other names)propsProperty, which is a function, and this function returns aReact Element, this function is called inside the component to complete rendering, so this component is usedrender propsTechnology.

Readers may wonder, “why do we need to callpropsProperty to achieve internal rendering of components, rather than directly rendering within components “? borrowReactOfficial reply,render propsNot everyReactThe skills that developers need to master, and even you may never use this method, but its existence does provide developers with an alternative when thinking about component code sharing.

Render PropsUsage scenarios

We may need to use pop-up frequently in project development. The pop-up UI can be changeable, but its function is similar, that isopenandclose。 withantdFor example:

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

  //Control pop-up display and hiding
  toggleModal = (visible) => {
    this.setState({ visible })
  };

  handleOk = (e) => {
    //Do something
    this.setState({ visible: false })
  }

  render() {
    const { visible } = this.state
    return (
      
        Open
        
          Some contents...
        
      
    )
  }
}

The above is the simplestModelWe still need to pay attention to its display state and realize its switching method even if it is simple to use. But developers only want to pay attention to those related to business logiconOk, the ideal way to use it should be as follows:

Open
  
    Some contents...

Can passrender propsAchieve the above usage:

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

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

  renderButton = (props) => 

  renderModal = ({ onOK, ...rest }) => (
     {
        onOK && onOK()
        this.toggle()
      }}
      onCancel={this.toggle}
    />
  )

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

In this way, we have completed a system with state and basic functionsModal, we use this on other pagesModalYou only need to pay attention to specific business logic.

As can be seen from the above,render propsIs a realReactComponents, not likeHOCSame, just oneFunctions that can return components, which also means usingrender propsNot likeHOCThere is no need to worry about the nesting of component levelspropsCoverage problems caused by naming conflicts.

render propsUse restrictions

stayrender propsShould be avoided inArrow function, because this will have a performance impact.

For example:

//Bad example
class MouseTracker extends React.Component {
  render() {
    return (
       (
        
      )}/>
    )
  }
}

It’s not good to write like this, becauserenderThe method is possible to render multiple times, usingArrow function, which will cause each rendering to pass inrenderThe values of will be different, but in fact there is no difference, which will lead to performance problems.

So a better way to write it is to pass inrenderThe function in is defined as an instance method, so even if we render many times, the bound function is always the same function.

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

  render() {
    return (
		  
    )
  }
}

render propsAdvantages and disadvantages of

  • advantage

    • Props naming can be modified, and there is no mutual coverage;
    • Know the source of props;
    • There will be no multi-level nesting of components;
  • shortcoming

    • Cumbersome writing;

    • Cannot be inreturnAccessing data outside the statement;

    • It is easy to produce function callback nesting;

      The following code:

      const MyComponent = () => {
        return (
          
            {({ x, y }) => (
              
                {({ x: pageX, y: pageY }) => (
                  
                    {({ api }) => {
                      // yikes
                    }}
                  
                )}
              
            )}
          
        )
      }

Hook

ReactThe core of is components, so,ReactHas been committed to optimizing and improving the way components are declared. From the earliestClass component, and thenFunction component, each has its advantages and disadvantages.Class componentIt can provide us with a complete life cycle and state, but it is very cumbersome in writing, andFunction componentAlthough the writing method is very simple and light, its limitation isIt must be a pure function, cannot contain state, and does not support lifecycle, soClass componentCannot replaceFunction component

andReactThe team feelsThe best way to write a component should be a function, not a class, resulting inReact Hooks

The design purpose of react hooks is to enhance the function components, and write a fully functional component without using “classes”

WhyClass component“Bulky”, borrowedReactOfficial examples:

import React, { Component } from "react"

export default class Button extends Component {
  constructor() {
    super()
    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 {buttonText}
  }
}

The above is a simple button component, including the most basic state and click method. After clicking the button, the state changes.

This is a very simple functional component, but it needs a lot of code to implement. becauseFunction componentState is not included, so we cannot use itFunction componentTo declare a component with the above functions. But we can useHookTo achieve:

import React, { useState } from "react"

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

  function handleClick() {
    return setButtonText("Thanks, been clicked!")
  }

  return {buttonText}
}

Comparatively speaking,HookIt is lighter and closer toFunction componentAt the same time, I kept my state.

The first hook is introduced in the above exampleuseState(), in addition,ReactThe official also provideduseEffect()useContext()useReducer()Wait for the hook. For details of specific hooks and their usage, seeofficial

HookIn addition to the official basic hooks, we can also use these basic hooks to encapsulate and customize hooks, so as to achieve easier code reuse.

Hook advantages and disadvantages

  • advantage
    • Easier to reuse code;
    • Refreshing code style;
    • Less code;
  • shortcoming
    • The state is not synchronized (functions run independently, and each function has an independent scope)
    • Need more reasonable useuseEffect
    • The granularity is small, and it needs to abstract a lot for complex logichook

summary

exceptMixinIn addition to being slightly backward due to its obvious defects, forHigh order componentsrender propsreact hookIn general, there is no way to callBest solution, they are both advantages and disadvantages. Even the hottestreact hook, although eachhookIt seems to be so brief and refreshing, but in actual business, usually one business function corresponds to multiplehook, which means that when the business changes, you need to maintain multiplehookTo maintain aclassIn general, the mental burden may increase a lot. Only the way that suits your business isBest solution


Reference documents:


Welcome to bump lab blog:aotu.io

Or follow aotulabs official account and push articles from time to time:

欢迎关注凹凸实验室公众号