Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class

In the past, functional components could not have their own state. They could only render their UI through props and context. In the business logic, some scenes must use state. Then we can only define functional components as class components. Now, with hooks, we can easily maintain our state in functional components without changing to class components.

React Hooks address the issue of state sharing, in which state logic reuse is only shared, not data sharing. We know that before React Hooks, we used higher-order components and render props for state reuse, so why did React developers introduce React Hooks when they already had them? What are the advantages of React Hook for higher-Order Components and render-props?

The React Hook example

Let’s take a look at the React Hook demo

import { useState } from 'React';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

React Hook Is not used

class Example extends React.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

In React Hook, the class Example component becomes a functional component. However, the functional component has its own state and can update its state. This is all thanks to the Hook useState, which returns a pair of values: the current state and a function that lets you update it, which you can call in an event handler or some other place. It is similar to the this.setState of the class component, but it does not merge the new state with the old state

ReactA solution for reuse of state logic

Hook is another solution for reusing state logic. React developers have been constantly proposing and improving reusing state logic, from mixins to higher-order components to Render Props to current hooks. Let’s take a brief look at the previous solutions

Mixinmodel

In the early days of React, it was proposed to reuse logic between components based on Mixin patterns. In Javascript, we can think of Mixin inheritance as a way to extend collection capabilities. Each new object we define has a stereotype from which it can inherit more properties. Stereotypes can be inherited from other objects, but more importantly, they can define properties for any number of objects. We can take advantage of this fact to promote functional reuse.

Mixins in React are mainly used in two completely unrelated components. A set of basically similar functions can be extracted and injected in the way of mixins, thus realizing code reuse. For example, if a component needs to be updated every once in a while in a different component, we can do this by creating a setInterval() function that needs to be uninstalled when the component is destroyed. So you can create a simple mixin that provides a simple setInterval() function that is automatically cleaned up when the component is destroyed.

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() { this.intervals.forEach(clearInterval); }}; var createReactClass = require('create-React-class');

var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // 使用 mixin
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() { this.setInterval(this.tick, 1000); // Call the mixin method}, tick:function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for{this.state.seconds} seconds. </p> ); }}); ReactDOM.render( <TickTock />, document.getElementById('example'));Copy the code

mixinThe disadvantages of the

  1. differentmixinThey may depend on each other and have strong coupling, resulting in high maintenance costs
  2. mixinMay have conflicting names and cannot use the same namemixin
  3. mixinEven if they start out simple, they snowball in complexity over time as business scenarios multiply

Mixins are a scourge

React does not recommend the use of mixin mode for code reuse. React fully recommends the use of higher-order components instead of mixin mode. Meanwhile, ES6 does not include any mixin support. Therefore, when you use ES6 classes in React, mixins are not supported.

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

Advanced components are not the React API, but a technique for implementing React. Advanced components can be seen as implementations of the Decorator Pattern in React. Decorator pattern: To dynamically attach responsibilities to objects, decorators provide a more flexible alternative to inheritance to extend functionality.

Specifically, a higher-order component is a function that takes a component and returns a new component.

A component converts props to UI, and a higher-order component converts a component to another component

We can dynamically add log printing to other components from higher-order components without affecting the functionality of the original component

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      return<WrappedComponent {... this.props} />; }}}Copy the code

Render Propss

The term “Render Props” refers to a simple technique for sharing code between React components using a prop with a value of function

A component with Render Props accepts a function that returns a React element and calls it instead of implementing its own Render logic

Below we provide a

component with prop that dynamically determines what needs to be rendered so that the logic and state of the

component can be reused without changing its rendering structure.

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return(<div> <h1> move mouse! </h1> <Mouse render={mouse => ( )}/> </div> ); }}Copy the code

However, usually we say Render Props because the mode is called Render Props, not because we have to name a prop with Render. We could also write it this way

<Mouse>
  {mouse => (
    <Cat mouse={mouse} />
  )}
</Mouse>
Copy the code

The React Hook motivation

React Hook is another new solution proposed by the official website. Before getting to know React Hook, let’s take a look at the motivation proposed by React Hook

  1. Reusing state logic between components is difficult
  2. Complex components become difficult to understand
  3. incomprehensibleclass

Here’s what I understand about these three motivations:

Reusing state logic between Components is difficult. Previously, we solved the problem of reusing state logic with higher-order Components and Render Propss. Many libraries use these patterns to reuse state logic, such as Redux and React Router. Higher-order components and render properties are nested layer by layer through composition of common components, which greatly increases the level of our code, resulting in excessive nesting of layers. React devTool clearly shows the degree of hierarchy nesting caused by using these two modes

Complex components become difficult to understand, and in the context of changing business requirements, components are increasingly overwhelmed with state logic and side effects, with each lifecycle often containing unrelated logic. We usually write code according to the rule of function singleness. A function usually does one thing, but in a lifecycle hook function it usually does many things at once. For example, when we need to make an Ajax request to get data in componentDidMount, and sometimes we write event bindings in this lifecycle, Sometimes even need the data in the componentWillReceiveProps like componentDidMount processing.

Code that is related and needs to be modified against each other is split, while completely unrelated code is grouped together in the same method. This is very buggy and leads to logical inconsistencies.

The class component can be used as a component of the class. If you understand the binding problem of this class, it is not difficult to get started. Just to be clear, this isn’t just React behavior; This has to do with how JavaScript functions work. So as long as you understand how JS functions work, this binding is not a problem. It’s just that sometimes we write a lot of code to bind this to make sure that this is pointing correctly, and if we forget to bind this, we get all kinds of bugs. Bind this method:

1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
   Click me
 </button>
Copy the code

React Hook was put forward to solve the above problems

State the hooks used

Let’s go back to the code and see how to define state in a functional component

import React, { useState } from 'React';
const [count, setCount] = useState(0);
Copy the code
  1. UseState did what

    We can see that in this function, we define a ‘state variable ‘through useState, which provides exactly the same functionality as this.state in class. Equivalent to the following code

    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    Copy the code
  2. UseState parameters

    In the code, we pass 0 as the useState argument, whose value will be taken as the initial count value. Of course, this argument is not limited to passing numbers and strings. You can pass an object as the initial state. If state needs to store the values of multiple variables, call useState multiple times

  3. UseState return value

    This is similar to this.state.count and this.setstate in class, except that you need to fetch them in pairs. It’s easy to see from [count, setCount] that this is ES6’s way of writing a deconstructed array. Equivalent to the following code

    let _useState = useState(0);// Returns an array of two elements
    let count = _useState[0];// First value in array
    let setCount = _useState[1];// Second value in the array
    Copy the code

Read status value

Just use variables

Before writing

<p>You clicked {this.state.count} times</p>
Copy the code

Now write

<p>You clicked {count} times</p>
Copy the code

Update the status

Update with setCount function

Before writing

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
 </button>
Copy the code

Now write

  <button onClick={() => setCount(count + 1)}>
    Click me
  </button>
Copy the code

Here setCount takes the new state value that has been modified

Declare multiple state variables

State hooks can be used multiple times in a component to declare multiple state variables

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}
Copy the code

React suppose that when you call it multiple timesuseStateYou can guarantee that they will be called in the same order every time you render

Why does React dictate that the call order remain the same every time it renders them? This is a crucial question to understand hooks

Rules of the Hook

A Hook is essentially a JavaScript function, but there are two rules to follow when using it. React requires that these two rules be enforced, otherwise exceptions will occur

  1. Use only at the top levelHook

Never call hooks in loops, conditions, or nested functions. Make sure you always call them at the top of your React function

  1. Only in theReactCall from a functionHook

Never call a Hook in a normal JavaScript function

The reason for these two rules is that you can use multiple State or Effect hooks in a single component. React relies on the order of Hook calls to know which State corresponds to which useState

function Form() {
  const [name1, setName1] = useState('Arzh1');
  const [name2, setName2] = useState('Arzh2');
  const [name3, setName3] = useState('Arzh3');
  // ...
}
// ------------
// First render
// ------------
useState('Arzh1')       // 1. Initialize the state of the variable named name1 with 'Arzh1'
useState('Arzh2')       // 2. Initialize state named name2 with 'Arzh2'
useEffect('Arzh3')     	// 3. Initialize state named name3 with 'Arzh3'

// -------------
// Secondary render
// -------------
useState('Arzh1')        // 1. Read the state of the variable named name1 (argument ignored)
useState('Arzh2')        // 2. Read the state of the variable named name2 (argument ignored)
useEffect('Arzh3')       // 3. Read state named name3 (argument ignored)

Copy the code

If we violate React rules, use conditional rendering

if(name ! = =' ') {
    const [name2, setName2] = useState('Arzh2');
}
Copy the code

Suppose the first time (name! When == “) is true, execute this Hook, and render the second time (name! If == ”) is false, do not execute this Hook, then the order of Hook calls will change, causing a bug

useState('Arzh1')        // 1. Read the state of the variable name1
//useState('Arzh2') // 2. Hook ignored
useEffect('Arzh3')       // 3. Read the state of variable named name2(previously name3)
Copy the code

React does not know what the second useState Hook should return. React would think that the second Hook call in this component would correspond to Arzh2’s useState as in the last render, but it doesn’t. So that’s why React enforces Hook use to follow these two rules, and we can use eslint-plugin-react-hooks to enforce constraints

Effect the hooks used

We added the use of Effect Hooks to the code above, added side effects to functional components, and changed the title of the page

  useEffect((a)= > {
    document.title = `You clicked ${count} times`;
  });
Copy the code

If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount.

The three health hook functions can be replaced by useEffect

Let’s take a look at the scenarios that typically require side effects, such as sending requests, manually changing the DOM, logging, and so on. We usually call our side effects after the first DOM rendering and subsequent DOM updates. We can look at previous life cycle implementations

  componentDidMount() {
    document.title = `You clicked The ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked The ${this.state.count} times`;
  }
Copy the code

This is one of the second problems with the React Hook motivation we mentioned above, requiring the same code to be called in the first render as well as in subsequent ones

By default, Effect is executed after the first rendering and after every update, so we don’t need to think about whether It’s componentDidMount or componentDidUpdate, just understand that Effect is executed after the component is rendered

Clearance side effect

Sometimes there are side effects that need to be removed. For example, when we have a requirement to poll the server for the latest status, we need to remove the polling operation during uninstallation.

  componentDidMount() {
    this.pollingNewStatus()
  }

  componentWillUnmount() {
    this.unPollingNewStatus()
  }
Copy the code

We can use Effect to remove these side effects by simply returning a function in Effect

  useEffect((a)= > {
    pollingNewStatus()
    // Tell React to run cleanup() before each render
    return function cleanup() {
      unPollingNewStatus()
    };
  });
Copy the code

The obvious difference is that useEffect is actually done before every render: cleanup(), while componentWillUnmount is only done once.

Effect performance optimization

UseEffect actually executes every update and can cause performance problems in some cases. We can optimize performance by skipping Effect. In the Class component, we can solve this by adding comparison logic to prevProps or prevState in componentDidUpdate

componentDidUpdate(prevProps, prevState) {
  if(prevState.count ! = =this.state.count) {
    document.title = `You clicked The ${this.state.count} times`; }}Copy the code

In Effect, we can simply add the second parameter to Effect and skip the update if there is no change

useEffect((a)= > {
  document.title = `You clicked ${count} times`;
}, [count]); // Update only when count changes
Copy the code

Other Hooks

Due to space reasons, I will not expand this, interested can view their official website

Refer to the article

  1. Learn React Hooks in 30 minutes
  2. The React hooks practice
  3. From Mixin to HOC to Hook