takeaway

Front-end development is very fast, pages and components become more and more complex, how to better realize the reuse of state logic has always been an important part of the application, which is directly related to the quality of the application and maintenance of the degree of difficulty.

This article introduces three technologies used by React to implement state logic reuse, and analyzes their implementation principles, application methods, practical applications, and how to choose to use them.

This article is a bit longer. Here is a mind map for this article, which you can read from the beginning or select the sections that interest you:

Mixin design patterns

Mixins are a way of extending collection. They essentially copy the properties of one object onto another, but you can copy any method of any number of objects onto a new object, which inheritance can’t. It appears mainly to solve the problem of code reuse.

Many open source libraries provide implementations of mixins, such as Underscore’s _. Extend method and JQuery’s extend method.

Code reuse using the _.extend method:

var LogMixin = {
  actionLog: function() {
    console.log('action... ');
  },
  requestLog: function() {
    console.log('request... '); }};function User() {  / *.. * /  }
function Goods() {  / *.. * / }
_.extend(User.prototype, LogMixin);
_.extend(Goods.prototype, LogMixin);
var user = new User();
var good = new Goods();
user.actionLog();
good.requestLog();
Copy the code

We can try writing a simple Mixin method by hand:

function setMixin(target, mixin) {
  if (arguments[2]) {
    for (var i = 2, len = arguments.length; i < len; i++) {
      target.prototype[arguments[i]] = mixin.prototype[arguments[i]]; }}else {
    for (var methodName in mixin.prototype) {
      if (!Object.hasOwnProperty(target.prototype, methodName)) {
        target.prototype[methodName] = mixin.prototype[methodName];
      }
    }
  }
}
setMixin(User,LogMixin,'actionLog');
setMixin(Goods,LogMixin,'requestLog');
Copy the code

You can extend any method of any object to the target object using the setMixin method.

React applies mixins

React also provides Mixin implementations that can be used to reuse code if a completely different component has similar functionality. This is only possible if the React component is created using createClass, since it is deprecated in the ES6 version of the React component.

For example, many components or pages need to record user behavior, performance metrics, and so on. If we introduce logging logic in every component, there will be a lot of duplicate code, which we can solve with mixins:

var LogMixin = {
  log: function() {
    console.log('log');
  },
  componentDidMount: function() {
    console.log('in');
  },
  componentWillUnmount: function() {
    console.log('out'); }};var User = React.createClass({
  mixins: [LogMixin],
  render: function() {
    return (<div>.</div>)}});var Goods = React.createClass({
  mixins: [LogMixin],
  render: function() {
    return (<div>.</div>)}});Copy the code

Mixins bring harm

React official documentation mentions the harm brought by Mixins in the article Mixins Considered Harmful:

  • MixinThey can be interdependent and coupled to each other, which is not good for code maintenance
  • differentMixinThe methods in the
  • MixinA lot of times, components are aware of it and even have to do something about it, which can snowball in code complexity

React no longer recommends mixins as a solution to code reuse because mixins cause more damage than value, and React fully recommends replacing them with higher-order components. In addition, there are many other more powerful functions that higher-order components can implement. Before we look at higher-order components, let’s look at a design pattern.

Decorative pattern

The Decorator pattern allows you to add responsibilities to objects dynamically during program execution without changing the objects themselves. Compared with inheritance, decorator is a more portable and flexible practice.

High order Component (HOC)

High-order components can be seen as an implementation of the React decorator pattern. High-order components are functions that take a component as an argument and return a new component.

HOC is an advanced technique in React that reuses component logic. But the higher-order components themselves are not the React API. It’s just a pattern, and that pattern is necessarily generated by the combinatorial nature of React itself.

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const{ visible, ... props } =this.props;
      if (visible === false) return null;
      return <WrappedComponent {. props} / >; }}}Copy the code

The above code is a simple application of HOC. The function receives a component as an argument and returns a new component. The new component can receive a Visible props and determine whether to render visible according to the value of visible.

Let’s explore HOC specifically from the following aspects.

HOC implementation

The property broker

Function returns a our own definition of components, and then return to the parcel in the render components, so that we can represent all incoming props, and decide how to render, in fact, the high order component is generated this way the original components of the parent component, the above function visible is a HOC property broker is implemented.

function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return <WrappedComponent {. this.props} / >; }}}Copy the code

Enhancements to native components:

  • Can manipulate all incomingprops
  • The life cycle of an operational component
  • Operable componentstaticmethods
  • To obtainrefs

Reverse inheritance

Return a component that inherits the original component and calls render in render. Because it inherits from the original component, it can access the lifecycle, props, state, render, and so on of the original component, and it can manipulate more properties than the property proxy.

function inheritHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      return super.render(); }}}Copy the code

Enhancements to native components:

  • Can manipulate all incomingprops
  • The life cycle of an operational component
  • Operable componentstaticmethods
  • To obtainrefs
  • operationalstate
  • Can render hijack

What can HOC do

Combination of rendering

You can use any other component and the original component to combine rendering to achieve style, layout reuse, and so on.

Through the property broker

function stylHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return (<div>
        <div className="title">{this.props.title}</div>
        <WrappedComponent {. this.props} / >
      </div>); }}}Copy the code

Through reverse inheritance

function styleHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      return <div>
        <div className="title">{this.props.title}</div>
        {super.render()}
      </div>}}}Copy the code

Conditions apply colours to a drawing

Determines whether or not the original component is rendered based on specific properties

Through the property broker

function visibleHOC(WrappedComponent) {
  return class extends Component {
    render() {
      if (this.props.visible === false) return null;
      return <WrappedComponent {. props} / >; }}}Copy the code

Through reverse inheritance

function visibleHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      if (this.props.visible === false) {
        return null
      } else {
        return super.render()
      }
    }
  }
}
Copy the code

Operating props

You can add, modify, delete, or perform special operations on the props of the component that is passed in.

Through the property broker

function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      constnewProps = { ... this.props,user: 'ConardLi'
      }
      return <WrappedComponent {. newProps} / >; }}}Copy the code

Get refs

In higher-order components, the ref of the original component can be obtained, through which the strength of the component can be obtained. The following code, when the program initialization is completed, calls the log method of the original component. (If you don’t know how to use refs, please visit 👇 refs & DOM)

Through the property broker

function refHOC(WrappedComponent) {
  return class extends Component {
    componentDidMount() {
      this.wapperRef.log()
    }
    render() {
      return <WrappedComponent {. this.props} ref={ref= >{ this.wapperRef = ref }} />; }}}Copy the code

Note here: calling higher-order components does not get the real ref of the original component, so you need to pass it manually. For details, see Passing refs

State management

The state of the original component is extracted into HOC for management. In the following code, we extract the value of the Input into HOC for management, making it a controlled component without affecting its use of the onChange method for some other operations. In this way, we can implement a simple bidirectional binding, see bidirectional binding.

Through the property broker

function proxyHoc(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = { value: ' ' };
    }

    onChange = (event) = > {
      const { onChange } = this.props;
      this.setState({
        value: event.target.value,
      }, () => {
        if(typeof onChange ==='function'){
          onChange(event);
        }
      })
    }

    render() {
      const newProps = {
        value: this.state.value,
        onChange: this.onChange,
      }
      return <WrappedComponent {. this.props} {. newProps} / >;
    }
  }
}

class HOC extends Component {
  render() {
    return <input {. this.props} ></input>
  }
}

export default proxyHoc(HOC);
Copy the code

The operating state

The above example enhances the original component with HOC’s state through the property broker, but without direct control over the original component’s state, we can directly manipulate the original component’s state through reverse inheritance. However, it is not recommended to directly modify or add the state of the original component, as this may conflict with operations within the component.

Through reverse inheritance

function debugHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      console.log('props'.this.props);
      console.log('state'.this.state);
      return (
        <div className="debuging">
          {super.render()}
        </div>)}}}Copy the code

HOC prints props and state in render and can be used for debugging, although you can write more debugging code in it. Imagine that we could debug the component we want to debug by simply adding @DEBUG to it, without having to write a lot of redundant code every time we debug it. (If you don’t know how to use HOC, please 👇 how to use HOC.)

Rendering hijacked

Higher-order components can do a lot of work in the render function to control the render output of the original component. Whenever we change the rendering of the original component, we call it a rendering hijack.

In fact, both combinational rendering and conditional rendering are renderjacking, which can be implemented by reverse inheritance and directly enhance the React element generated by the render function of the original component.

Through reverse inheritance

function hijackHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      const tree = super.render();
      let newProps = {};
      if (tree && tree.type === 'input') {
        newProps = { value: 'Rendering has been hijacked' };
      }
      const props = Object.assign({}, tree.props, newProps);
      const newTree = React.cloneElement(tree, props, tree.props.children);
      returnnewTree; }}}Copy the code

Note above that I used enhancements, not changes. The render function actually produces the React element by calling React. CreatElement:

getOwnPropertyDescriptors

You can see that all writable properties are configured to false, meaning that all properties are immutable. (For questions about these configuration items, please visit 👇defineProperty)

Instead of modifying it directly, we can augment a new component with the cloneElement method:

React. CloneElement () clones and returns a new React element, using Element as the starting point. The resulting element will have a shallow merge of the original elements props and the new props. The new child replaces the existing child. Keys and refs from the original elements are retained.

React.cloneelement () is almost equivalent to:

<element.type {... element.props} {... props}>{children}</element.type>Copy the code

How to Use HOC

The above example code is all about declaring HOC. HOC is actually a function, so we call HOC as an argument to the enhanced component to get the enhanced component.

class myComponent extends Component {
  render() {
    return (<span>The original component</span>)}}export default inheritHOC(myComponent);
Copy the code

compose

In practical applications, a component may be enhanced by multiple HOC. We used the component enhanced by all HOC. To illustrate this, it is easier to understand by using a diagram of decoration patterns:

Suppose we now have multiple HOC, logger, visible, style, and so on, and now want to enhance an Input component at the same time:

logger(visible(style(Input)))
Copy the code

This code is very difficult to read, so we can manually wrap a simple function composition tool and rewrite it as follows:

const compose = (. fns) = > fns.reduce((f, g) = >(... args) => g(f(... args))); compose(logger,visible,style)(Input);Copy the code

The compose function returns a function composed of all functions, the compose(f, g, h) and (… args) => f(g(h(… Args))) is the same.

Many third-party libraries provide compose functions, such as Lodash. flowRight and The combineReducers function provided by Redux.

Decorators

We can also use the Decorators provided by ES7 to make our writing more elegant:

@logger
@visible
@style
class Input extends Component {
  // ...
}
Copy the code

Decorators is a proposal for ES7 that has not been standardized yet, but Babel transcoders are now supported. We need to pre-configure babel-plugin-transform-decorators-legacy:

"plugins": ["transform-decorators-legacy"]
Copy the code

You can also use the compose function above:

const hoc = compose(logger, visible, style);
@hoc
class Input extends Component {
  // ...
}
Copy the code

Practical use of HOC

The following are some actual application scenarios of HOC in the production environment. Due to the length of the article, the code has been simplified a lot. Please point out in the comments section if you have any questions:

Dot log

In fact, this is one of the most common applications where multiple components have similar logic and we need to reuse repeated logic. The examples of CommentList in the official CommentList document address code reuse in great detail. If you are interested, 👇 can use HOC to address crosscutting concerns.

Some pages need to record user behavior, performance metrics, and so on, and doing this with higher-level components saves a lot of repetitive code.

function logHoc(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      this.start = Date.now();
    }
    componentDidMount() {
      this.end = Date.now();
      console.log(`${WrappedComponent.dispalyName}Render time:The ${this.end - this.start} ms`);
      console.log(`${user}Enter the${WrappedComponent.dispalyName}`);
    }
    componentWillUnmount() {
      console.log(`${user}exit${WrappedComponent.dispalyName}`);
    }
    render() {
      return <WrappedComponent {. this.props} / >}}}Copy the code

Availability and permission control

function auth(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, auth, display = null. props } =this.props;
      if (visible === false || (auth && authList.indexOf(auth) === - 1)) {
        return display
      }
      return <WrappedComponent {. props} / >; }}}Copy the code

AuthList is the list of all permissions that we request from the back end when we enter the program. When the required permissions for the component are not in the list, or when visible is set to False, we display them as the component style that was passed in, or null. We can apply HOC to any component that requires permission verification:

@auth class Input extends Component { ... } @auth class Button extends Component { ... <Button auth="user/addUser"> </Button> <Input auth="user/search" visible={false} >Copy the code

Two-way binding

In VUE, bidirectional data binding can be achieved by binding a variable, that is, the bound variable changes automatically when the value in the form changes. React doesn’t do this; by default, form elements are uncontrolled components. After binding a state to a form element, you often need to manually write the onChange method to rewrite it into a controlled component, which can be painful to repeat if there are too many form elements.

We can implement a simple bidirectional binding with higher-order components. The code is slightly longer and can be understood with the mind map below.

First we define a custom Form component that wraps all Form components that need to be wrapped, exposing two properties to the child components via Contex:

  • model: the currentFormControl all data by formnameandvalue, such as{name:'ConardLi',pwd:'123'}.modelIt can be imported from outside or controlled by itself.
  • changeModelChange:modelIn a certainnameThe value of the.

class Form extends Component {
  static childContextTypes = {
    model: PropTypes.object,
    changeModel: PropTypes.func
  }
  constructor(props, context) {
    super(props, context);
    this.state = {
      model: props.model || {}
    };
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.model) {
      this.setState({
        model: nextProps.model
      })
    }
  }
  changeModel = (name, value) = > {
    this.setState({
      model: { ...this.state.model, [name]: value }
    })
  }
  getChildContext() {
    return {
      changeModel: this.changeModel,
      model: this.props.model || this.state.model
    };
  }
  onSubmit = (a)= > {
    console.log(this.state.model);
  }
  render() {
    return <div>
      {this.props.children}
      <button onClick={this.onSubmit}>submit</button>
    </div>}}Copy the code

Here’s a definition of HOC for bidirectional binding that proxies the onChange and value attributes of the form:

  • happenonChangeEvent to call the upper layerFormthechangeModelMethods to changecontextIn themodel.
  • At render time willvalueInstead of fromcontextThe value extracted from.
function proxyHoc(WrappedComponent) {
  return class extends Component {
    static contextTypes = {
      model: PropTypes.object,
      changeModel: PropTypes.func
    }

    onChange = (event) = > {
      const { changeModel } = this.context;
      const { onChange } = this.props;
      const { v_model } = this.props;
      changeModel(v_model, event.target.value);
      if(typeof onChange === 'function'){onChange(event); } } render() {const { model } = this.context;
      const { v_model } = this.props;
      return <WrappedComponent
        {. this.props}
        value={model[v_model]}
        onChange={this.onChange}
      />;
    }
  }
}
@proxyHoc
class Input extends Component {
  render() {
    return <input {. this.props} ></input>}}Copy the code

The above code is only a brief part. In addition to input, we can also apply HOC to other form components such as SELECT, and even make the above HOC compatible with span, table and other presentation components. This can greatly simplify the code and save us a lot of state management work.

export default class extends Component {
  render() {
    return (
      <Form >
        <Input v_model="name"></Input>
        <Input v_model="pwd"></Input>
      </Form>)}}Copy the code

Form validation

Based on the two-way binding example above, let’s have a form validator. The form validator can contain a validation function and a message that displays an error message if the validation fails:

function validateHoc(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = { error: ' ' }
    }
    onChange = (event) = > {
      const { validator } = this.props;
      if (validator && typeof validator.func === 'function') {
        if(! validator.func(event.target.value)) {this.setState({ error: validator.msg })
        } else {
          this.setState({ error: ' ' })
        }
      }
    }
    render() {
      return <div>
        <WrappedComponent onChange={this.onChange}  {. this.props} / >
        <div>{this.state.error || ''}</div>
      </div>}}}Copy the code
const validatorName = {
  func: (val) = > val && !isNaN(val),
  msg: 'Please enter a number'
}
const validatorPwd = {
  func: (val) = > val && val.length > 6.msg: 'Password must be greater than 6 characters'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>
Copy the code

You can also check whether all the validators have passed or not when the Form is submitted. You can also set the validators to arrays, etc. Because of the length of this article, the code has been simplified a lot.

Redux of the connect

Connect in Redux is simply HOC. Here is a simplified version of connect:

export const connect = (mapStateToProps, mapDispatchToProps) = > (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super(a)this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      store.subscribe((a)= > this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props): {} 
      let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props) : {} 
      this.setState({
        allProps: {... stateProps, ... dispatchProps, ... this.props } }) } render () {return <WrappedComponent {. this.state.allProps} / >
    }
  }
  return Connect
}
Copy the code

The connect function does just one thing: it deconstructs the mapStateToProps and mapDispatchToProps and passes them to the source component, so that we can get the state and dispatch functions directly in the source component.

Considerations for using HOC

Caveat – Static property copy

When we apply HOC to enhance another component, the component we’re actually using is no longer the original component, so we don’t get any static properties of the original component, we can manually copy them at the end of HOC:

function proxyHOC(WrappedComponent) {
  class HOCComponent extends Component {
    render() {
      return <WrappedComponent {. this.props} / >;
    }
  }
  HOCComponent.staticMethod = WrappedComponent.staticMethod;
  // ... 
  return HOCComponent;
}
Copy the code

If the original component has a lot of static properties, this process can be painful, and you need to know what the static properties of all components are that need to be enhanced. We can solve this problem by using hoist non-React statics, which automatically copies all non-React static methods. The usage is as follows:

import hoistNonReactStatic from 'hoist-non-react-statics';
function proxyHOC(WrappedComponent) {
  class HOCComponent extends Component {
    render() {
      return <WrappedComponent {. this.props} / >;
    }
  }
  hoistNonReactStatic(HOCComponent,WrappedComponent);
  return HOCComponent;
}
Copy the code

Caveat – Pass refs

With higher-order components, the ref retrieved is actually the outermost container component, not the original component, but in many cases we need to use the ref of the original component.

Higher-order components can’t pass refs through like transparent props, so we can use a callback function to pass refs:

function hoc(WrappedComponent) {
  return class extends Component {
    getWrappedRef = (a)= > this.wrappedRef;
    render() {
      return <WrappedComponent ref={ref= >{ this.wrappedRef = ref }} {... this.props} />; } } } @hoc class Input extends Component { render() { return<input></input> }
}
class App extends Component {
  render() {
    return (
      <Input ref={ref= > { this.inpitRef = ref.getWrappedRef() }} ></Input>); }}Copy the code

React 16.3 provides the forwardRef API for refs transfer. Refs can be refs from the refs of higher order components without having to pass refs manually.

function hoc(WrappedComponent) {
  class HOC extends Component {
    render() {
      const{ forwardedRef, ... props } =this.props;
      return<WrappedComponent ref={forwardedRef} {... props} />; } } return React.forwardRef((props, ref) => { return <HOC forwardedRef={ref} {... props} />; }); }Copy the code

Caveat – Do not create higher-order components within the Render method

The React Diff algorithm follows the following principles:

  • Use the component identity to determine whether to uninstall or update the component
  • If the component’s identity is the same as the previous rendering, recursively update the child component
  • Remount a new component if it identifies a different unmounted component

Each call to a higher-order component generates a new component, and the unique identifier of the component response changes. If the higher-order component is called in the Render method, this will cause the component to be unloaded and remounted each time.

Convention – Do not change the original component

Official documentation for advanced components:

A higher-order component is a pure function with no side effects.

Let’s look at the definition of a pure function:

If the function is called with the same arguments, the same result is always returned. It does not depend on any changes in state or data outside the function during program execution and must depend only on its input parameters. This function does not produce any observable side effects, such as network requests, input and output devices, or data mutations.

If we make changes to the original component in higher-order components, such as the following code:

InputComponent.prototype.componentWillReceiveProps = function(nextProps) {... }Copy the code

This breaks our convention for higher-order components and also changes the purpose of using higher-order components: we use higher-order components to enhance, not change, the original component.

Convention – Pass-through of unrelated props

With higher-order components, we can delegate all props, but often a particular HOC will only use one or more of them. We need to pass the other unrelated props to the original component as follows:

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const{ visible, ... props } =this.props;
      if (visible === false) return null;
      return <WrappedComponent {. props} / >; }}}Copy the code

We only use the visible property to control the component’s display-hide and pass the other props through.

Agreed – displayName

When debugging with React Developer Tools, the debugging interface can become very difficult to read if we use HOC, as shown in the following code:

@visible
class Show extends Component {
  render() {
    return <h1>I am a label</h1>
  }
}
@visible
class Title extends Component {
  render() {
    return <h1>I'm a headline</h1>}}Copy the code

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

static displayName = `Visible(${WrappedComponent.displayName}) `
Copy the code

This convention helps ensure maximum flexibility and reusability for higher-order components.

Motivation for using HOC

To review the risks of mixins mentioned above:

  • MixinThey can be interdependent and coupled to each other, which is not good for code maintenance
  • differentMixinThe methods in the
  • MixinA lot of times, components are aware of it and even have to do something about it, which can snowball in code complexity

The emergence of HOC can solve these problems:

  • A higher-order component is a pure function with no side effects; the higher-order components do not depend on each other for coupling
  • Higher-order components can also cause conflicts, but we can avoid these behaviors by following conventions
  • Higher-order components don’t care how or why the data is used, and wrapped components don’t care where the data comes from. The addition of higher-order components does not burden the original components

The defect of HOC

  • HOCNeed to wrap or nest on top of the original component if used in large quantitiesHOC, will generate a lot of nesting, which makes debugging very difficult.
  • HOCCan be hijackedpropsConflicts can also occur when agreements are not followed.

Hooks

Hooks are a new feature in React V16.7.0-alpha. It lets you use state and other React features outside of class.

Using Hooks, you can abstract the logic containing state from the component, which makes it easy to test. Also, Hooks help you reuse this logic without rewriting the component structure. Therefore, it can also be used as a scheme to realize state logic reuse.

Read the following section on the motivation for using Hook to see how it solves both Mixin and HOC problems.

Official Hooks

State Hook

To implement a counter function using the class component, we might write:

export default class Count extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={()= > { this.setState({ count: this.state.count + 1 }) }}>
          Click me
        </button>
      </div>)}}Copy the code

With useState, we can also do this using functional components:

export default function HookTest() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > { setCount(count + 1); setNumber(number + 1); }}>
        Click me
        </button>
    </div>
  );
}
Copy the code

UseState is a hook that adds states to functional components, provides functions to change those states, and takes a parameter as the default value of the state.

Effect Hook

Effect Hooks allow you to perform side effects within function components

parameter

The useEffect method receives two arguments:

  • 1. Callback function: once in the first componentrenderAnd every time sinceupdateAfter running,ReactEnsure that theDOMThe callback will not run until the update is complete.
  • 2. State dependencies (arrays) : When a state dependency is configured, the callback function is called only when a change in the state of the configuration is detected.
  useEffect((a)= > {
    // execute as soon as the component render
  });
  useEffect((a)= > {
    // Execute only if count changes
  },[count]);
Copy the code

Callback return value

The first argument to useEffect returns a function that will be called after the page renders the results of the next update and before the next useEffect is executed. This function is often used to clean up the last call to useEffect.

export default function HookTest() {
  const [count, setCount] = useState(0);
  useEffect((a)= > {
    console.log('execution... ', count);
    return (a)= > {
      console.log('clean up... ', count);
    }
  }, [count]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > { setCount(count + 1); setNumber(number + 1); }}>
        Click me
        </button>
    </div>
  );
}
Copy the code

Executing the code above and clicking the button a few times gives the following result:

Note that if you add browser rendering, the result should look like this:

Page rendering..1.Perform...1Page rendering..2.Clean up...1Perform...2Page rendering..3.Clean up...2Perform...3Page rendering..4.Clean up...3Perform...4
Copy the code

So why is it that after the browser has rendered, the cleanup method can still find the last state? The reason is simple: we return a function in useEffect, which forms a closure that ensures that the variables stored in the last function we executed are not destroyed or contaminated.

You can try the following code to get a better understanding

    var flag = 1;
    var clean;
    function effect(flag) {
      return function () {
        console.log(flag);
      }
    }
    clean = effect(flag);
    flag = 2;
    clean();
    clean = effect(flag);
    flag = 3;
    clean();
    clean = effect(flag);

    // Execution result

    effect... 1
    clean... 1
    effect... 2
    clean... 2
    effect... 3
Copy the code

Simulation componentDidMount

The callback to componentDidMount is equivalent to useEffect, which is executed only once after page initialization, when useEffect’s second argument is passed an empty array.

function useDidMount(callback) {
  useEffect(callback, []);
}
Copy the code

This is not officially recommended because it can lead to errors.

Simulation componentWillUnmount

function useUnMount(callback) {
  useEffect((a)= > callback, []);
}
Copy the code

Unlike componentDidMount or componentDidUpdate, the effect used in useEffect does not block browser rendering of the page. This makes your app look smoother.

ref Hook

Using the useRef Hook, you can easily get the DOM ref.

export default function Input() {
  const inputEl = useRef(null);
  const onButtonClick = (a)= > {
    inputEl.current.focus();
  };
  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}
Copy the code

Note that useRef() is not just used to fetch refs. The ref current attribute produced by using useRef is mutable, which means you can use it to hold an arbitrary value.

Simulation componentDidUpdate

ComponentDidUpdate = useEffect (); useRef (); useEffect (); useEffect ();

function useDidUpdate(callback, prop) {
  const init = useRef(true);
  useEffect((a)= > {
    if (init.current) {
      init.current = false;
    } else {
      return callback();
    }
  }, prop);
}

Copy the code

Considerations for using hooks

Using range

  • Only in theReactFunctional components or customizationHookThe use ofHook.

Hooks are designed to solve the problems of class components, so we can use them in class components.

The statement constraints

  • Do not call hooks in loops, conditions, or nested functions.

React needs to use the call order to update the corresponding state correctly. If useState is wrapped in a loop or conditional statement, the call order may be out of order every time, resulting in unexpected errors.

We can install an ESLint plug-in to help us avoid these problems.

/ / installation
npm install eslint-plugin-react-hooks --save-dev
/ / configuration
{
  "plugins": [
    // ...
    "react-hooks"]."rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"}}Copy the code

Customize the Hook

Like HOC and mixin, we can also extract similar state logic from components through custom hooks.

Custom hooks are very simple. We only need to define a function and encapsulate the required state and effect. At the same time, hooks can refer to each other. Custom hooks are named starting with use to make it easier for ESLint to check.

Let’s look at some specific Hook encapsulation:

Dot log

We can use the lifecycle hooks encapsulated above.

const useLogger = (componentName, ... params) = > {
  useDidMount((a)= > {
    console.log(`${componentName}Initialize `. params); }); useUnMount((a)= > {
    console.log(`${componentName}Uninstall `. params); }) useDidUpdate((a)= > {
    console.log(`${componentName}Update `. params); }); };function Page1(props){
  useLogger('Page1',props);
  return (<div>.</div>)}Copy the code

Change the title

Modify page title according to different page names:

function useTitle(title) {
  useEffect(
    (a)= > {
      document.title = title;
      return (a)= > (document.title = "Home page");
    },
    [title]
  );
}
function Page1(props){
  useTitle('Page1');
  return (<div>.</div>)}Copy the code

Two-way binding

We extract the logic of the form onChange and encapsulate it into a Hook so that all form components that need bidirectional binding can be reused:

function useBind(init) {
  let [value, setValue] = useState(init);
  let onChange = useCallback(function(event) { setValue(event.currentTarget.value); } []);return {
    value,
    onChange
  };
}
function Page1(props){
  let value = useBind(' ');
  return <input {. value} / >;
}
Copy the code

Of course, you can combine context and form to encapsulate a more general two-way binding, just as you did with HOC above, and you can do this manually.

Motivation to use hooks

Reduce the risk of state logic reuse

Hooks and mixins have some similarities in usage, but the logic and state introduced by mixins can overwrite each other, and multiple hooks do not affect each other, so we do not need to focus part of our efforts on preventing conflicts that avoid logic reuse.

Using HOC without conforming to the convention can also lead to conflicts, such as props overrides, which can be avoided by using hooks.

Avoid hell of nesting

Heavy use of HOC made our code very deeply nested. Using Hook, we can achieve flat reuse of state logic without heavy component nesting.

Make components easier to understand

As we build our programs using class components, they each have their own state, and the complexity of business logic makes these components bigger and bigger, with more and more logic called in each lifecycle and harder to maintain. Using hooks allows you to extract more common logic and split a component into smaller functions rather than forcing a life-cycle approach to split.

Use functions instead of classes

Writing a class may require more knowledge than writing a function, with more points to watch out for, such as this pointing, binding events, and so on. In addition, computers can understand a function faster than they can understand a class. Hooks allow you to use more of React’s new features outside of classes.

Rational choice

In fact, the stable version of Hook was officially released in React 16.8.0, and the author has not used it in production environment. Currently, the author uses HOC most in production environment.

React has no plans to remove classes from React at all. Classes and hooks can coexist, and it is recommended to avoid any “extensive refactoring”. This is a very new version, so you can use hooks in new, non-critical code if you like.

summary

Mixin has been abandoned, HOC is in its prime, Hook is just showing its edge, front-end circle is like this, the speed of technological iteration is very fast, but we must understand why to learn these knowledge, whether it is useful to learn, whether to use. Never forget why you started, and your mission can be accomplished.

If there are any mistakes in this article, please correct them in the comments section. Thank you for reading.

Recommended reading

  • 【React in-Depth 】setState execution mechanism
  • React Event mechanism

Recommend attention

To read more excellent articles, or to read the mind mapping source files for articles, follow my Github blog at star✨.

I recommend you to pay attention to my wechat public number [Code Secret Garden], we grow up together.

After concern public number reply [add group] pull you into high quality front end communication group.