5

When any project develops to a certain degree of complexity, it will inevitably face the problem of logic reuse. There are usually several ways to implement logic reuse in React Mixin , high-end component (HOC), decorator, Render Props , Hook . 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 the first method that developers who have just switched Vue React Mixin has been widely used in various object-oriented languages. is used to create an effect similar to multiple inheritance for single inheritance languages, . Although React has now abandoned it, Mixin was indeed React achieve code sharing.

The generalized mixin method is to mount all the methods in the mixin object to the original object by way of assignment to achieve the mixing of objects, similar to the role 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) {
    // 遍历mixins的属性
    if (mixins.hasOwnPrototype(prop)) {
      // 判断是否为mixin的自身属性
      newObj.prototype[prop] = mixins[prop]; // 赋值
    }
  }
  return newObj
};

Use Mixin in React

Assuming that in our project, multiple components need to set the default name attribute. Using mixin can save us from writing multiple same getDefaultProps methods in different components. We can define a mixin :

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

In order to use mixin , you need to add the mixins attribute to the component, and then mixin we wrote into an array, and use it as the attribute value of mixins

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

written by mixin can be reused in other components.

Since the mixins is an array, it means that we can call multiple mixin same component. In the above example, make a slight change to get:

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

we can even in a mixin contains other's mixin .

For example, write a new mixin `DefaultProps containing the above DefaultNameMixin and DefaultFriendMixin`:

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

At this point, we can conclude that mixin at least the following advantages:

  • can use the same mixin in multiple components;
  • mixin in the same component;
  • may be the same mixin nested in a plurality mixin ;

But in different scenarios, advantages may also become disadvantages:

  • destroy the original package assembly, may need to maintain a new state and props like state ;
  • different from mixin . The naming in 0609b51c13c2eb is unknowable, and it is very easy to conflict ;
  • may have recursive call problems, which increases the complexity of the project and the difficulty of maintenance ;

In addition, mixin has its own processing logic for issues such as state conflicts, method conflicts, and the calling sequence of multiple life cycle methods. Interested students can refer to the following articles:

High-end components

Since mixin these limitations, it React stripped mixin , instead higher order components to replace it.

higher-order component is essentially a function, which accepts a component as a parameter and returns a new component .

React high-end components when implementing some public components, such as react-router in withRouter , and Redux in connect . Take withRouter as an example.

By default, this.props Route route matching must exist to have 0609b51c13c524 and only have routing parameters, in order to use functional navigation to execute this.props.history.push('/next') jump to the page of the corresponding route. higher order components withRouter action is not a Route assembly wrapped route package to Route inside, thereby react-router three objects history , location , match put into the assembly props properties, it can be realized so Functional navigation jump.

The realization principle of withRouter

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

Use code:

import React, { Component } from "react"
import { withRouter } from "react-router"
class TopHeader extends Component {
  render() {
    return (
      <div>
        导航栏
        {/* 点击跳转login */}
        <button onClick={this.exit}>退出</button>
      </div>
    )
  }

  exit = () => {
    // 经过withRouter高阶函数包裹,就可以使用this.props进行跳转操作
    this.props.history.push("/login")
  }
}
// 使用withRouter包裹组件,返回history,location等
export default withRouter(TopHeader)

Since the essence of the high-level component getting the component and returning the new component, it can theoretically realize multiple nesting mixin

E.g:

Write a higher-order function that empowers 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 />
        }
    }
}

Write a higher-order function that empowers 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 />
        }
    }
}

Use the above high-end components

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

class Joy extends Component {
  render() {
    return <div>Joy</div>
  }
}

// 给Joy赋能唱歌和跳舞的特长
export default widthSinging(withDancing(Joy))

It can be seen from the above that just a simple package with a high-order function can turn the originally simple Joy into a little nightclub prince who can both sing and dance!

Use HOC conventions

When using HOC , there are some conventional conventions:

  • Pass irrelevant Props to the packaging component (pass props that have nothing to do with their specific content);
  • Step-by-step combination (avoid different forms of HOC serial calls);
  • Contains the displayed displayName to facilitate debugging (each HOC should conform to the rule's display name);
  • Do not render function (each time you render, the higher-order returns a new component, which affects the diff performance);
  • The static method must be copied (the new component returned by the higher order will not contain the static method of the original component);
  • Avoid using ref (ref will not be passed);

Advantages and disadvantages of HOC

So far we can summarize the advantages of high-order components (HOC):

  • HOC is a pure function, easy to use and maintain;
  • Also, since HOC is a pure function, it supports the input of multiple parameters to enhance its scope of application;
  • HOC returns a component, which can be combined and nested and has strong flexibility;

Of course, HOC also has some problems:

  • When a plurality HOC when nested, can not be directly determined subassembly props which from HOC responsible for transmission;
  • When the parent and child components have the same name props , it will cause the parent component to overwrite the child component with the same name props , and react will not report an error, and the developer's perception is low;
  • Each HOC returns a new component, which produces a lot of useless components, and at the same time deepens the component level, which is not convenient for troubleshooting;

modifier and the high-end components belong to the same mode, so I will not discuss them here.

Render Props

Render Props is a very flexible and highly reusable mode. It can encapsulate specific behaviors or functions into a component, and provide it to other components for use by other components.

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

This is the official React Render Props , which is translated into the vernacular: " Render Props is a technology to React Components props component contains a props attribute to implement the attribute of the function Internal rendering logic".

Official example:

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

As above, the DataProvider component has a props render (also can be called other names), which is a function, and this function returns a React Element , which is used to complete the rendering by calling this function inside the component. render props technology.

Readers may wonder, "Why do we need to call the props property to achieve internal rendering of the component, instead of directly completing the rendering in the component"? Borrowing React official reply, render props not every React when developers need to master the skills, you may never even use this method, but it is there really for developers thinking about code-sharing component of the problem, providing One more choice.

Render Props usage scenarios

We may need to frequently use pop-up windows in project development. The pop-up window UI can be ever-changing, but the functions are similar, that is, open and closed. Take antd as an example:

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

  // 控制弹窗显示隐藏
  toggleModal = (visible) => {
    this.setState({ visible })
  };

  handleOk = (e) => {
    // 做点什么
    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>
    )
  }
}

The above is the simplest Model . Even if it is simple to use, we still need to pay attention to its display status and realize its switching method. But developers actually only want to pay attention to onOk related to business logic. The ideal way to use it should be like this:

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

The above usage can be achieved through 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
    })
  }
}

In this way, we have completed a Modal Modal on other pages, we only need to pay attention to the specific business logic.

As can be seen above, render props is a real React components, rather than HOC as just a can return function component , this also means that render props not like HOC the same generation assembly-level nested problem, do not worry props Overwrite problems caused by naming conflicts.

render props usage restrictions

In render props should avoid using arrow function, as this will affect performance.

such as:

// 不好的示例
class MouseTracker extends React.Component {
  render() {
    return (
      <Mouse render={mouse => (
        <Cat mouse={mouse} />
      )}/>
    )
  }
}

It is not good to write this way, because the render method is likely to be rendered multiple times. Using the arrow function render to be different each time it is rendered, but there is actually no difference, which will lead to Performance issues.

So a better way is to render as an instance method, so that even if we render multiple times, the binding is always the same function.

// 好的示例
class MouseTracker extends React.Component {
  renderCat(mouse) {
      return <Cat mouse={mouse} />
  }

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

The advantages and disadvantages of render props

  • advantage

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

    • Cumbersome writing;
    • Cannot access data outside the return
    • Easy to produce function callback nesting;

      The following code:

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

Hook

React is components. Therefore, React has been committed to optimizing and perfecting the way of declaring components. From the earliest class components to function components, each has its own advantages and disadvantages. class component can provide us with a complete life cycle and state (state), but it is very cumbersome in writing. function component is very simple and light, its limitation is that must be a pure function and cannot contain state. is also not supported, so the type component cannot replace the function component.

The React team felt that component should be a function, not the class , which resulted in React Hooks .

React Hooks is to enhance the functional components. You can write a fully functional component without using "classes" at all.

Why is it said that class components are "cumbersome", borrowing the official example of React

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 <button onClick={this.handleClick}>{buttonText}</button>
  }
}

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 requires a lot of code to implement. Since the function component does not contain state, we cannot use the function component to declare a component with the above functions. But we can use Hook to 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 <button onClick={handleClick}>{buttonText}</button>
}

In comparison, Hook appears to be lighter, and it retains its own state while function component.

In the above example, the first hook useState() is introduced. In addition, the React official also provides hooks such as useEffect() , useContext() , useReducer() For specific hooks and their usage details, please see official .

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

Hook pros and cons

  • advantage

    • Easier to reuse code;
    • Refreshing code style;
    • Less code;
  • Disadvantage

    • State is not synchronized (functions run independently, each function has an independent scope)
    • Need more reasonable use useEffect
    • The granularity is small, and a lot of complex logic needs to be abstracted hook

to sum up

Except that Mixin is slightly behind because of its own obvious defects, for high-end components, render props , react hook , there is no way to call the best solution for . They all have advantages and disadvantages. Even the most popular react hook , although each hook looks so short and refreshing, but in actual business, usually one business function corresponds to multiple hook , which means that when the business changes, you need to go Maintaining multiple hook changes, compared to maintaining one class , the mental burden may increase a lot. 's own business is the best solution for 1609b51c13d5ad.


Reference documents:


Welcome to follow the blog of Bump Lab: aotu.io

Or follow the AOTULabs official account (AOTULabs) and push articles from time to time.


凹凸实验室
2.3k 声望5.5k 粉丝

凹凸实验室(Aotu.io,英文简称O2) 始建于2015年10月,是一个年轻基情的技术团队。