Logic reuse in React components

Logical reuse in React mainly involves three aspects: render prop, HOC, and custom Hook. These three are most commonly used in our actual projects. Let’s give a detailed overview of their respective application scenarios, as well as their similarities and differences.

render props

Render attribute is a way to reuse logic in React. It can reuse the internal states and logic of components. By providing a functional prop, it can determine how to render the components and achieve the ability to reuse the internal states and logic of the components.

// Mouse tracking function, Class MouseTracker extends React.Com constructor(props) {super(props) this.state = {x:0, class MouseTracker extends React.Com constructor(props) {super(props) this.state = {x:0, HandleMouseMove (event) {this.setState({x: event.clientx, y: event.clienty}); } render() {<div style={{height: '100vh'}} onMouseMove={this.handlemousemove}> < / h1 > < p > the current mouse position is ({this. State. X}, {this. State. Y}) < / p > < / div >}}Copy the code

How can we reuse the logic of mouse tracing if we need it in other components? The difficulty here is that the mouse state and mouse tracking logic are inside this component, so how can we use these things in other components?

A good example is given in the official documentation: we want a cat 🐱Can always track the mouse position; Obviously mouse tracking logic is required to implement this functionality, which brings us back to the question raised above, how can we reuse itMouseTrackerWhat about state and logic in components?

 <Cat style={{ position: 'absolute', left: mouseInfo.x, top: mouseInfo.y }}></Cat>
Copy the code

It seems that we can retrieve the state and logic of MouseTracker by placing the Cat component inside the MouseTracker component. There are usually two ways to display a component inside another component: One is to pass Cat as a child, via props. Children :


; The second is to modify the original MouseTracker component and add the Cat component directly to the MouseTracker component. So the Cat component is put in, but neither approach satisfies our needs:

The first way: although the Cat component can now be represented in the MouseTracker component through props. Children, how do I set the mouseInfo of the mouse in the MouseTracker component to the
component? But the way it’s written, there’s no chance of setting it up.

<MouseTracker> <Cat/> </MouseTracker> Get the class MouseTracker extends react.ponent {// state information...... inside the MouseTracker component via props. Children render() { <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> { this.props.children } </div> } }Copy the code

The second way: it is possible to set the mouse information by changing the original MouseTracker component, but changing the original component does not achieve the effect of reuse of the component. It can only be used in a specific use case, and the next time you use it, you still need to wrap a new component.

Class MouseTracker extends React.Component{// State info...... render() { <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> <Cat style={{left: this.state.x, top: this.state.y }}></Cat> </div> } }Copy the code

Back to the above mentioned render prop, the parent component in the form of a function to pass in the content to render, but also can reuse the state and logic in the MouseTracker component, to achieve code reuse function;

Class MouseTracker extends React.Component{// State info...... render() { <div style={{ height: '100vh'}} onMouseMove={this.mousemove}> {this.props. Render (this.state)} </div>}} <MouseTracker render={(mouseInfo) => <Cat style={{left: mouseinfo.x, top: mouseinfo.y}}></Cat>} />Copy the code

Render Prop is called Render Prop because of the mode, and you don’t have to use a prop named Render to use this mode. In fact, any function prop that is used to tell a component what to render is technically called a Render Prop.

The props. Children function can be used as a function to receive the state information and return the component content to be rendered. In this way, the internal state and logic of the component can be reused.

Class MouseTracker extends React.Component{// State info...... render() { <div style={{ height: '100vh'}} onMouseMove={this.handlemousemove}> Pass state inside a child component {this.props. Children (state)} </div>}} //children give a function <MouseTracker render={(mouseInfo) => <Cat style={{ left: mouseInfo.x, top: mouseInfo.y }}></Cat>}> { state => <Cat style={{ left: state.x, top: state.y }}></Cat> } </MouseTracker>Copy the code

High order component

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.

Specifically, a higher-order component is a function that takes a component as an argument and returns another new component:

function higherOrderComponent (WrappedComponent) { return class extends React.Component { constructor(props) { super(props) this.state = { /**other Data*/} } render() { return <WrappedComponent {... this.props} data={... this.state}/> } } } const EnhancedComponent = higherOrderComponent(WrappedComponent);Copy the code

Two ways to implement HOC

The property broker

The so-called attribute proxy is to wrap another layer of proxy component in the outer layer of the business component to be enhanced. On the proxy component, we can do some proxy operations on the original business component. In terms of the life cycle, call componentDidMount of the business component first and then call componentDidMount of the outer proxy component, so the relationship between the outer proxy component and the wrapped business component is parent-child component. On the code side, HOC returns a container component (either a stateful class component or a stateless function component) containing the business component that needs to be enhanced. HOC does not modify an incoming component, nor does it use inheritance to replicate its behavior. Instead, HOC combines components into new components by wrapping them in container components. HOC is a pure function with no side effects. The wrapped component receives all the prop from the container component, and can also receive a new data prop for Render. HOC doesn’t need to care about how or why the data is being used, and the packaged component doesn’t need to care about how the data came from. Higher-order components generated in this way are as follows:

  1. Function of reuse

Direct overall reuse of business component functions, which is the most fundamental function of higher-order components; But don’t try to modify the wrapped component, as this can make its later functionality unpredictable:

class WrappedComponent extends React.Component { shouldComponentUpdate() { return true } render() { return <div>WrappedComponent</div>}} Function HOC1 (WrappedComponent) {return class extends React.Com constructor(props) {super(props) This. State = {/ * * other Data * /}} / / change the method of the original component WrappedComponent. Prototype. ShouldComponentUpdate = function () {return false } render() { return <WrappedComponent {... this.props} data={... this.state}/> } } }Copy the code

In the use of higher-order components, it changes the logic of the original component by prototyping outside the component. The next time the component is referenced again, it is referenced with the changed component, but we do not know where the logic was changed (because it was not in the current component).

  1. Props attribute hijacking

Can operate on props passed in from outside (except ref and key, which are special and are processed separately and not passed down). If you add ref and key to a component returned by HOC, the ref and key references refer to the outer container component, not the wrapped component). Props can be added, deleted, or modified before being passed to the packaged component:

function higherOrderComponent (WrappedComponent, otherData) { return function(props) { const newProps = { ... props, ... otherData, name: 'update value', } return <WrappedComponent {... newProps} /> } }Copy the code
  1. Rendering hijacked

    Depending on the attribute values passed in from the outside, you can determine how the component displays (handling loading, no data… When to display) :

Function higherOrderComponent (WrappedComponent) {return class extends React.Component {render() {if( /** Render condition */) {return null} else if(/** render condition */) {return 'loading'} else return <WrappedComponent {... this.props}/> } } }Copy the code
Function higherOrderComponent (WrappedComponent) {return class extends React.Component {render() {return (); The < div > {/ * combination render content * /} < div > {this. Props. The title} < / div > < WrappedComponent {... this.props}/> </div> ) } } }Copy the code
  1. enhancements

This is also kind of an operation on props; What if we want some component to have a certain function? Those of you who have used the Redux library know that after a component passes through Connect, there will be a dispatch method inside the component. Have you ever wondered where this dispatch came from when using Redux? This dispatch is actually passed in externally via props during high-level component reinforcement.

Higher-order components implemented in the property proxy approach also have some shortcomings or impossibilities:

  1. The internal State of a business component cannot be obtained directly

    Normally within components we get state directly through this.state; If you want to get the state inside a component from outside the component, you can only get it from ref, so if you want to get the state of a wrapped component in a higher-order component, you can only get it indirectly by setting ref on the wrapped component.

  2. Static methods of business components cannot be invoked directly

    In ES6, static properties or methods provided by a class can only be called by the class itself, not by the instance object of the class. When a business component is strengthened by HOC, it returns another new component, which of course does not have the static properties of the packaged component.

    Class WrappedComponent extends React.Component {staticMethod() {/* staticMethod */} render() {return <div>WrappedComponent</div>}} // HOC enhanced const EnhancedComponent = HOC(WrappedComponent); EnhancedComponent.staticMethod() // EnhancedComponent.staticMethod is not a functionCopy the code

    If we want to use the static methods provided by the inner business component in the outer container component, we have to manually copy these static methods to the outer container component:

    function HOC (WrappedComponent) { class EhancedComponent extends React.Component { render() { return <WrappedComponent { . This. Props} / >}} / / manually copy the inner static method of business component to outer container components EhancedComponent. StaticMethod = WrappedComponent. StaticMethod; return EhancedComponent }Copy the code
2. Reverse inheritance

Reverse inheritance is also through a function that takes a component as an argument and returns another new component. The difference from the property broker is that the returned component inherits the received parameter component and calls render() of the parameter component in the render method of the returned component.

Function HOC (WrappedComponent) {class EhancedComponent extends WrappedComponent {// Extends WrappedComponent render() {return Super.render () // Call the render method in the parent class with super}}Copy the code

Since EhancedComponent and WrappedComponent inherit from each other, So in subclass EhancedComponent you can get properties and methods (state, props, key, ref, static method, lifecycle) from the parent class WrappedComponent. If the subclass does not have a property or method of the same name, the subclass will get its own property or method.

High-order components realized by property proxy have functions (function reuse, props property hijacking, rendering hijacking, and function enhancement), which have reverse inheritance. And features that the former does not have (no direct access to the internal state of the wrapped component, no direct call to the static method of the wrapped component, no manipulation of the life cycle of the wrapped component…) , the latter are also available, but reverse inheritance can only be used to enhance class components, not function components;

In terms of usage, property proxy is more like starting from outside the WrappedComponent and using props to implement the desired functionality, whereas reverse inheritance is more like starting directly inside the WrappedComponent and manipulating the inherited properties to implement the desired functionality.

Customize the hook

React introduced hooks after V16.8. By custom Hook, component logic reuse can be realized. Usually when we want to share logic between two functions, we extract it into a third function. Components and hooks are functions, so the same applies. The difference with normal functions is that custom hooks must be named starting with use; At the same time, custom hooks can call other hooks internally, and can accept any parameter or return any type value we want.

import { useState, useEffect } from 'react'; Function useFriendStatus(friendID) {const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }Copy the code

Since the main function of custom hook is to realize logic reuse and reduce the writing of the same logic code. There is nothing wrong with this statement, but this function can be done with an ordinary function, right? Extract the same logic from components into a function, and then call the function where it is needed. Why does React introduce a custom hook? ComponentDidMount, componentDidUpdate, componentUnMount