I. Introduction to Hook

1.1 Hook history

Prior to React Hook, there were two main types of components: functional components and class components. The functional component is usually only responsible for rendering the UI. It has no state of its own and no business logic code. It is a pure function. Class components, on the other hand, have their own internal state, and the interface display is usually determined by props and state, so they are not so pure. Functional components, class components have the following disadvantages:

  • State logic is hard to reuse. In class components, schemes such as render props or hoc are proposed to reuse some of the state logic, but these schemes are too intrusive, and component nesting is prone to the problem of nested hell.
  • Misuse of component state. Most developers write components as class components, regardless of whether the component has internal state or does not perform lifecycle functions, which incurs unnecessary performance overhead.
  • Extra task handling. When developing applications using class components, developers need to pay extra attention to this, adding and removing event listeners, and so on.

While functional components are popular, class components are gradually being phased out. However, functional components are not without their drawbacks, and managing the state sharing of functional components was a bit of a problem in the previous writing. For example, the following function component is a pure function whose output is determined only by the props parameter and not by any other factors.

function App(props) {
  const {name, age } = props.info
  return (
      <div style={{ height: '100%' }}>
        <h1>Hello,i am ({name}),and i am ({age}) old</h1>
      </div>
  )
}
Copy the code

In the above functional component, once we need to add state to the component, we have to rewrite the component as a class component because the functional component has no instance and no life cycle. So the biggest difference between a function component and a class component before Hook is the presence or absence of state.

1.2 an overview of the Hook

To address the issue of functional component state, React added the Hook feature in version 16.8, which allows developers to use state and other React features without writing classes. Also, if you use React Native for mobile app development, React Native supports hooks starting with version 0.59.

In addition, after using Hook, we can extract state logic to make components testable and reusable, and developers can reuse state logic without changing the component hierarchy, so as to better realize the purpose of separating state and logic. Here is an example of using State Hook.

import React, { useState } from "react";

const StateHook = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>you clicked {count} times</p>
      <button type="button" onClick={() => setCount(count + 1)}>
        click me
      </button>
    </div>
  );
};

Copy the code

In the example above, useState is a Hook, which adds some internal State to the component by calling it in the function component. React retains this State during repeated rendering. UseState 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. (We’ll show an example of comparing useState with this. State in using State Hook).

Ii. Basic concepts of Hook

React Hook Provides state for functional components. It allows functions to fetch data, subscribe to events, unbind events, etc. Before learning React Hook, let’s understand some basic concepts.

2.1 useState

UseState gives function components the ability to be stateful. For example, the previous counter example uses useState.

  function App (a) {
    const [count, setCount ] = useState(0)
    return<button onClick={() => {<div> button onClick={() => {setCount(count + 1</button> </div>)}Copy the code

As you can see, useState is very simple to use. The first value is our state, and the second value is a function to modify the value of that state. UseState supports specifying a default value for state, such as useState(0), useState({a: 1}), as well as passing in a logically calculated default value, such as.

function App (props) {
    const [ count, setCount ] = useState(() = > {return props.count || 0
    })
    return(...). }Copy the code

2.2 useEffect

Effect Hook lets you handle side effects in function components. In React, fetching data, setting up subscriptions, and manually changing the DOM can all be called side effects, which can be divided into two types: those that need to be cleaned up and those that don’t. Don’t clean up side effects like network requests, DOM changes, and logging. For example timers, event listeners need to be handled, and useEffect allows developers to handle these side effects.

Here is an example of using useEffect to change the document.title title.

import React, { useState,useEffect } from "react";

function App (a) {
    const [ count, setCount ] = useState(0)

    useEffect(() => {
        document.title = count
    })

    return<button onClick={() => {setCount(count + 1</button> </div>)}export default App;
Copy the code

UseEffect Hook (componentDidMount, componentDidUpdate, and componentWillUnmount).

ComponentDidMount, componentDidUpdate, componentWillUnmount life cycle. UseEffect works just like these three lifecycle functions, except that it is passed as an argument to decide whether to call it or not. UseEffect returns a callback function that cleans up the state left over from the previous side effect. If useEffect is called only once, the callback is equivalent to the componentWillUnmount life cycle.

For example, here is an example of a useEffect synthesis.

import React, { useState,useEffect } from "react";

function App (a) {
    const [ count, setCount ] = useState(0)
    const [ width, setWidth ] = useState(document.body.clientWidth)

    const onChange = () => {
        setWidth(document.body.clientWidth)
    }

    useEffect(() = > {// equivalent to componentDidMount
        window.addEventListener('resize', onChange, false)
        return() = > {// equivalent to componentWillUnmount
            window.removeEventListener('resize', onChange, false)}}, [])useEffect(() = > {// equivalent to componentDidUpdate
        document.title = count;
    })

    useEffect(() => {
        console.log(`count change: count is ${count}`)
    }, [ count ])

    return{width} <button onClick={() => {setCount(count + 1</button> </div>)}export default App;
Copy the code

In the example above we need to deal with two side effects: we need to deal with title and we need to listen for screen width changes. The way the class component is written we need to deal with this logic during the lifecycle, but in the Hooks we just need to use useEffect to fix this.

As mentioned earlier, useEffect is used to deal with side effects, and cleaning up the state left over from the previous session is one of its functions. UseEffect is used as the second parameter of the useEffect function since it is called after every render, the title change is equivalent to componentDidUpdate, but we don’t want the event to listen for binding and unbinding after every render.

When will the second parameter useEffect be used? The main scenarios are as follows:

  • UseEffect is called every time a component executes Render, which is equivalent to executing the componentDidMount and componentDidUpdate life cycles of the class component.
  • UseEffect is called only once when passed an empty array [], which is equivalent to executing the class component’s componentDidMount and componentWillUnmount life cycles.
  • Pass in an array of variables, and useEffect is executed only if those variables change.

2.3 useMemo

In traditional function components, when a child component is called from a parent component, the parent component will be updated due to the change of state of the parent component, and the child component will be updated even though it is not changed. UseMemo is the method adopted by function components to prevent such unnecessary updates. It acts like a PureComponent for a class component.

Here’s an example of how useMemo is used.

function App (a) {
  const [ count, setCount ] = useState(0)
  const add = useMemo(() = > {return count + 1
  }, [count])
  return<button onClick={() => {<div> button onClick={() => {setCount(count + 1</button> </div>)}Copy the code

Note that useMemo differs from useEffect in that it executes at render time, not after, so useMemo does not recommend side effect logic in methods.

2.4 useCallback

UseCallback is the syntactic sugar of useMemo. Almost anything implemented with useCallback can use useMemo, but useCallback has its own usage scenarios. For example, in React, we often faced the problem of rendering optimization of subcomponents. Especially when passing functions to subcomponents, new functions would be created each time for rendering, resulting in unnecessary rendering of subcomponents. UseCallback uses a cached function, which reduces unnecessary rendering when passing this cached function as props to child components.

import React, { useState, useCallback, useEffect } from 'react';
function Parent(a) {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState("); const callback = useCallback(() => { return count; }, [count]); Return 
      

Parent: Button onClick={() => setCount(count +1)}>

; } function Child({ callback }) { const [count, setCount] = useState(() => callback()); useEffect(() => { setCount(callback()); }, [callback]); Return
child component: {count}
} export default Parent;
Copy the code

Note that the react. memo and react. useCallback should be used together. If either memo is missing, the performance will drop instead of increase.

2.5 useRef

In React, we use a Ref to get a component instance or DOM element. We can create a Ref in two ways: createRef and useRef, as shown below.

import React, { useState, useRef } from 'react'

function App(a){
    const [count, setCount] = useState(0)
    const counterEl = useRef(null)

    const increment = () => {
        setCount(count + 1)
        console.log(counterEl)
    }

    return<> Count: <span ref={counterEl}>{Count}</span> <button onClick={increment}>Copy the code

2.6 useReducer

The functions of useReducer are similar to those of Redux. Compared with useState, useReducer is suitable for situations with more complex logic and multiple subvalues. The Reducer accepts two parameters, the first is a Reducer and the second is the initial state, and the return values are the latest state and dispatch functions.

Officially, useReducer is suitable for complex state operation logic, nested state objects scenarios. Here are the official examples.

import React, { useReducer } from 'react';

function Reducers (a) {
    const initialState={count:0}
    const [count,dispatch] = useReducer((state,avtion) => {
        switch(avtion.type) {
            case 'add':
                return state+1;
            case 'minus':
                return state- 1
            default:
                return state
        }
    },0)
    return (
        <div>
            <div>{count}</div>
            <button onClick={() => {dispatch({type: 'add'</button> <button onClick={() => {dispatch({type: 'minus'</button> </div>)}export default Reducers
Copy the code

2.7 useImperativeHandle

UseImperativeHandle lets developers customize the instance values exposed to the parent component when using ref. UseImperativeHandle can optionally expose methods to the parent component and hide private methods and properties. UseImperativeHandle is best used with the forwardRef.

import React, { useRef, forwardRef, useImperativeHandle } from 'react'

const App = forwardRef((props,ref) => {

    const inputRef = useRef(a)useImperativeHandle(ref,()=>({
        focus : () =>{
            inputRef.current.focus()
        }
    }),[inputRef])
    return <input type="text" ref={inputRef}/>
})

export default function Father() {
    const inputRef = useRef(a)return (
        <div>
            <App ref={inputRef}/>
            <button onClick={e=>inputRef.current.focus</button> </div>)}Copy the code

In the example, we use useImperativeHandle to output the instance properties of the child component to the parent component, while the child component will not be rerendered after changing the current object within the child via ref, requiring changing the state of the useState setting.

In addition to the Hook apis described above, React Hook apis include useLayoutEffect and useDebugValue.

Customize the Hook

With the use of Hook technology, React function components’ This pointing and lifecycle logic redundancy problems have been solved. However, another common problem in React development is the reuse of logic code. If you want to solve this problem, you need to customize the Hook.

The so-called custom Hook actually refers to the function whose name starts with use and calls other hooks. Each state of the custom Hook is completely independent. For example, here is an example of implementing a network request using a custom Hook wrapped around AXIOS.

import axios from 'axios'
import { useEffect, useState} from 'react';

const useAxios = (url, dependencies) => {

    const [isLoading, setIsLoading] = useState(false);
    const [response, setResponse] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() = > {setIsLoading(true);
        axios.get(url).then((res) => {
            setIsLoading(false);
            setResponse(res);
        }).catch((err) => {
            setIsLoading(false);
            setError(err);
        });
    }, dependencies);
    return [isLoading, response, error];
}

export default useAxios;
Copy the code

In the code above, we use the React API to implement custom hooks. However, the use method of custom Hook is similar to that of the Official Hook API provided by React, as shown below.

function App(a) {
    let url = 'http://api.douban.com/v2/movie/in_theaters';
    const [isLoading, response, error] = useAxios(url, []);

    return( <div> {isLoading ? <div>loading... </div> : (error ? <div> There is an error happened </div> : <div> Success, {response} </div>)} </div> ) }export default App;
Copy the code

It can be found that, compared with function properties and higher-order components, custom hooks are more concise and easy to read. Moreover, custom hooks do not cause the problem of component nesting hell.

Although the React Hooks have many advantages. However, in using Hooks, note the following:

  • Do not use hooks in loops, conditions, or nested functions, and only use hooks at the top of React functions. The reason for doing this is that React needs to use the call order to update the corresponding state correctly and call the corresponding lifecycle functions. Once a Hook is called in a loop or conditional branch statement, it is easy to cause inconsistencies in the order of calls, with unpredictable consequences.
  • You can only use hooks in React functional components or custom hooks.

In the meantime, to avoid some low-level errors during development, you can install an esLint plugin, using the following command.

yarn add eslint-plugin-react-hooks --dev
Copy the code

Then, add the following configuration to the ESLint configuration file.

{
  "plugins": [
    // ...
    "react-hooks"]."rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"."react-hooks/exhaustive-deps": "warn"}}Copy the code