With React hooks, you can spend more time fishing

🎙️ preface

According to legend, react 17 has a very powerful function, that is, react hooks. In fact, react hooks is a bit similar to vue3's composition API. They all appear to improve development efficiency.

Then, in the following article, we will start from 0 to 1 to show you about react hooks and some common API s.

No more nonsense, start the react hook trip~

I 📻 summary

1. About React Hooks

  • React Hooks is an optional function, which is usually compared with the class component;
  • 100% backward compatibility, no destructive changes;
  • Class components will not be replaced, and there are no plans to remove class components.

2. Meet React Hooks

(1) Review React functional components

Let's first review the class component and function component, as follows:

class component:

// class component
class List extends React.Component {
    constructor() {
        super(props)
    }
    render() {
        const { List } = this.props
        
        return <ul>{list.map((item, index) => {
            return <li key={item.id}>
                	<span>{item.title}</span>
                </li>
        })}</ul>
    }
}

Function component:

// Function component
function List(props) {
    const { list } = props
    
    return <ul>{list.map((item, index) => {
        return <li key={item.id}>
            	<span>{item.title}</span>
            </li>
    })}</ul>
}

(2) Features of function components

Function components are characterized by:

  • No component instance;
  • No life cycle;
  • Without state and setState, only props can be received.

(3) class component problem

As mentioned above, the function component is a pure function, which can only receive props without any other functions. The class component has the above functions, but the class component has the following problems:

  • Large components are difficult to split, reconstruct and test (that is, class is not easy to split);
  • The same business logic is scattered into various methods, and the logic is chaotic;
  • Reuse logic becomes complex, such as Mixins, HOC, Render Props.

Therefore, with the emergence of the above problems, there are React Hooks.

(4) React component

  • React components are easier to express with functions:

  • React advocates functional programming, that is, view=fn(props);

  • Functions are more flexible, easier to split and easier to test;

  • But function components are too simple and need to be enhanced -- so, with React Hooks.

II 🪕 Several Hooks

1,State Hook🗞️

(1) Let the function component implement state and setState

  • The default function component has no state;
  • The function component is a pure function, which is destroyed after execution, and the store cannot be stored;
  • State Hook is required, that is, the store function is "hooked" into a pure function.

(2) Illustrate with examples

Suppose we now want to modify a set value by clicking the button. Now we use useState in hooks. The specific codes are as follows:

import React, { useState } from 'react'

function ClickCounter() {
    // Deconstruction of arrays
    // useState is a Hook "Hook", the most basic Hook
    const [count, setCount] = useState(0) // Pass in an initial value, which can be numeric / string / array / object, etc

    const [name, setName] = useState('Monday Lab')

    // const arr = useState(0)
    // const count = arr[0]
    // const setCount = arr[1]

    function clickHandler() {
        setCount(count + 1)
        setName(name + '2021')
    }

    return <div>
        <p>You clicked {count} second {name}</p>
        <button onClick={clickHandler}>click</button>
    </div>
}

export default ClickCounter

The browser displays as follows:

In the above code, count is the value of a state, and setCount is a function to modify the state. Similarly, name is also a state value, and setName is a function that modifies the value of name.

If we use hooks to modify the state value, we only need to modify the final value in the form of const [count, setCount] = useState(0), instead of using it as complex as the class component.

For the above function, if the class component is used to implement it, the specific code is as follows:

import React from 'react'

class ClickCounter extends React.Component {
    constructor() {
        super()

        // Define state
        this.state = {
            count: 0,
            name: 'Monday Lab'
        }

        this.clickHandler = this.clickHandler.bind(this)
    }
    render() {
        return <div>
            <p>You clicked {this.state.count} second {this.state.name}</p>
            <button onClick={this.clickHandler}>click</button>
        </div>
    }
    clickHandler() {
        // Modify state
        this.setState({
            count: this.state.count + 1,
            name: this.state.name + '2021'
        })
    }
}

export default ClickCounter

You can see that if you use the class component to solve the problem, you need to define state first, then define a function, and then use setState to modify the value. This seems to be a little troublesome.

I believe that here, we have felt the joy of hooks.

Next, let's summarize some knowledge about useState.

(3) useState Usage Summary

  • useState(xxx) passes in the initial value and returns the array [state, setState];
  • Get the value through state;
  • Modify the value through setState(xxx).

(4) Hooks naming convention

  • It is stipulated that all Hooks should start with use, such as useXxx;
  • Custom Hook should also start with use;
  • In places other than Hooks, try not to use useXxx, otherwise it is easy to cause misunderstanding.

2,Effect Hook🗞️

(1) Let function components simulate the lifecycle

  • The default function component has no life cycle;
  • Function component is a pure function, which is destroyed after execution, and it cannot realize the life cycle by itself;
  • Use Effect Hook to "hook" the life cycle into pure functions.

(2) Illustrate with examples

Similarly, we use the same example of useState to experience useEffect.

Let's first create a file under SRC components, named liftcycles js . The specific codes are as follows:

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

function LifeCycles() {
    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    // Simulate the DidMount and DidUpdate of the class component
    useEffect(() => {
        console.log('Send a message here ajax request')
    })

    function clickHandler() {
        setCount(count + 1)
        setName(name + '2020')
    }

    return <div>
        <p>You clicked {count} second {name}</p>
        <button onClick={clickHandler}>click</button>
    </div>
}

export default LifeCycles

Now, we're at app JS. The specific codes are as follows:

import React, { useState } from 'react';
import LifeCycles from './components/LifeCycles'

function App() {
  const [flag, setFlag] = useState(true)

  return (
    <div
      {flag && <LifeCycles/>}
    </div>
  );
}

export default App;

At this time, the display effect of the browser is as follows:

As you can see, if only one function is passed in, useEffect simulates the two life cycle functions of DidMount and DidUpdate. Every time we click once, the browser will print once, that is, component loading and component updating are carried out together.

What if we want to separate component loading and component updating. Let's transform liftcycles JS code. The specific codes are as follows:

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

function LifeCycles() {
    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    // Simulate the DidMount of the class component
    useEffect(() => {
        console.log('Finished loading')
    }, []) // The second parameter is [] (independent of any state)

    // Simulate DidUpdate of class component
    useEffect(() => {
        console.log('Updated')
    }, [count, name]) // The second parameter is the dependent state

    function clickHandler() {
        setCount(count + 1)
        setName(name + '2020')
    }

    return <div>
        <p>You clicked {count} second {name}</p>
        <button onClick={clickHandler}>click</button>
    </div>
}

export default LifeCycles

The browser displays as follows:

As you can see, it was loaded and updated once for the first time. When we click, it will be updated because it has been loaded.

Let's sort out how useEffect handles loading and updating respectively?

Looking at the above code, we can find that useEffect can also receive the second parameter. If the second parameter is null, it does not depend on any state, indicating the life cycle of componentDidMount. On the contrary, if the second parameter has no dependent value or receives a state dependent value, it simulates the life cycle of componentDidUpdate.

Speaking of this, some small partners may also want to ask, what about the life cycle related to destruction? Let's continue with liftcycles JS code. The details are as follows:

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

function LifeCycles() {
    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    // Simulate the DidMount of the class component
    useEffect(() => {
        let timerId = window.setInterval(() => {
            console.log(Date.now())
        }, 1000)

        // Returns a function
        // Simulate WillUnMount
        return () => {
            window.clearInterval(timerId)
        }
    }, [])

    function clickHandler() {
        setCount(count + 1)
        setName(name + '2020')
    }

    return <div>
        <p>You clicked {count} second {name}</p>
        <button onClick={clickHandler}>click</button>
    </div>
}

export default LifeCycles

You can see the above code. Here, we use the return function to simulate the life cycle of componentWillUnMount to destroy the tasks executed in each timer.

Here, I believe you have a certain understanding of useEffect. Now, let's make a summary of useEffect.

(3) useEffect Usage Summary

  • Simulate componentDidMount - useEffect dependency [];
  • Simulate componentDidUpdate - useEffect has no dependency or depends on [a, b];
  • Simulate a function returned in componentWillUnMount - useEffect.

(4) useEffect has side effects on pure functions

  • By default, when executing a pure function, you only need to enter parameters and return results without any side effects;
  • The so-called side effects are the effects outside the function, such as setting global timing tasks;
  • Components need side effects, so useEffect needs to be "hooked" into pure functions;
  • Therefore, the side effect of useEffect is not a bad thing.

(5) Return function fn in useEffect

One noteworthy situation is that we simulate WillUnMount by returning a function, but the result of this simulation is not completely equal to WillUnMount. Now, we create a file under src|components and name it friendstatus js . The specific codes are as follows:

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

function FriendStatus({ friendId }) {
    const [status, setStatus] = useState(false)

    // DidMount and DidUpdate
    useEffect(() => {
        console.log(`Start listening ${friendId} Online status`)

        // [special attention]
        // This is not exactly the same as WillUnMount
        // When props changes, that is, updates will also end listening
        // To be exact: the returned function will be executed before the next effect execution
        return () => {
            console.log(`End listening ${friendId} Online status`)
        }
    })

    return <div>
        Good friend {friendId} Online status:{status.toString()}
    </div>
}

export default FriendStatus

App.js code is as follows:

import React, { useState } from 'react';
import FriendStatus from './components/FriendStatus'

function App() {
  const [flag, setFlag] = useState(true)
  const [id, setId] = useState(1)

  return (
    <div>
      <div>
        <button onClick={() => setFlag(false)}>flag = false</button>
        <button onClick={() => setId(id + 1)}>id++</button>
      </div>

      {flag && <FriendStatus friendId={id}/>}
    </div>
  );
}

export default App;

At this time, the display effect of the browser is as follows:

As you can see, when you start the next monitor, you will end the previous monitor first and then start the next one. As can be seen in the figure, at the beginning, we were listening to a friend with id 1. When we want to click the id + + button to listen to friend 2, useEffect will first end the state of 1 and then let friend 2 go online.

At this time, we should pay attention to the fact that the function that ends listening executes the DidUpdate life cycle instead of the WillUnMount life cycle. At this time, the function is in the update state rather than the destroy state.

Based on the above, let's make a summary. The details are as follows:

  • When useEffect depends on [], execute the return function fn when the component is destroyed, which is equal to the WillUnMount life cycle;
  • When useEffect does not depend or depends on [a, b], the component is a function fn that executes return when updating; That is, FN will be executed before the next useEffect. At this time, the DidUpdate life cycle is simulated.

3. Other Hooks 🗞️

Above, we talked about two commonly used hooks → useState and useEffect. Next, let's look at some other less commonly used hooks.

(1)useRef

Ref is used to modify values and obtain DOM elements. Let's start with a code:

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

function UseRef() {
    const btnRef = useRef(null) // Initial value

    const numRef = useRef(0)
    numRef.current = 2;

    useEffect(() => {
        console.log(btnRef.current) // DOM node
        console.log(numRef.current); // Get value
    }, [])

    return <div>
        <button ref={btnRef}>click</button>
    </div>
}

export default UseRef

At this time, the display effect of the browser is:

As you can see, if btnRef is bound to ref={btnRef}, btnRef Current gets the current DOM node.

In another case, you can locate numRef. If we assign a value to it and modify it, then numRef The final value of current is the modified value.

(2)useContext

Many times, we often need to switch some static attributes, such as theme color switching. At this time, the context needs to be used for processing. In hook, useContext can handle this matter. Let's start with a demo code:

import React, { useContext } from 'react'

// Theme color
const themes = {
    light: {
        foreground: '#000',
        background: '#eee'
    },
    dark: {
        foreground: '#fff',
        background: '#222'
    }
}

// Create Context
const ThemeContext = React.createContext(themes.light) // Initial value

function ThemeButton() {
    const theme = useContext(ThemeContext)

    return <button style={{ background: theme.background, color: theme.foreground }}>
        hello world
    </button>
}

function Toolbar() {
    return <div>
        <ThemeButton></ThemeButton>
    </div>
}

function App() {
    return <ThemeContext.Provider value={themes.dark}>
        <Toolbar></Toolbar>
    </ThemeContext.Provider>
}

export default App

The browser displays as follows:

Now it belongs to the theme of black background. If we want to switch to the theme with white background, we just need to change the bottom code value={themes.dark} to value={themes.light}.

(3)useReducer

In Redux, we will use reducer, but useReducer is a little different from redux. useReducer is the status of a single component

Let's start with a code:

import React, { useReducer } from 'react'

const initialState = { count: 0 }

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 }
        case 'decrement':
            return { count: state.count - 1 }
        default:
            return state
    }
}

function App() {
    // Much like const [count, setCount] = useState(0)
    const [state, dispatch] = useReducer(reducer, initialState)

    return <div>
        count: {state.count}
        <button onClick={() => dispatch({ type: 'increment' })}>increment</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
    </div>
}

export default App

The browser displays as follows:

We put the value to be changed into the reducer, and then pass the reducer to useRouter. Finally, the value is bound through dispatch.

Let's sort out the differences between useproducer and redux:

  • useReducer is an alternative to useState, which is used to handle complex changes in state;
  • useReducer is a single component state management, and props is also required for component communication;
  • redux is a global state management, where multiple components share data.

(4)useMemo

useMemo is part of performance optimization in hooks. When we do not use useMemo, the state is like this. Look at the following code:

import React, { useState } from 'react'

// Subcomponents
function Child({ userInfo }) {
    console.log('Child render...', userInfo)

    return <div>
        <p>This is Child {userInfo.name} {userInfo.age}</p>
    </div>
}

// Parent component
function App() {
    console.log('Parent render...')

    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    const userInfo = { name, age: 20 }

    return <div>
        <p>
            count is {count}
            <button onClick={() => setCount(count + 1)}>click</button>
        </p>
        <Child userInfo={userInfo}></Child>
    </div>
}

export default App

At this time, the printing effect of the console is as follows:

You can see that every time we click once, the Child sub component will reprint whether the state value is updated or not. This is more performance consuming for programs. Therefore, we should use useMemo to prevent Child events from printing frequently and improve the performance of the program. Code modification is as follows:

import React, { useState, memo, useMemo } from 'react'

// Subcomponents
// Similar to class PureComponent, shallow comparison of props
const Child = memo(({ userInfo }) => {
    console.log('Child render...', userInfo)

    return <div>
        <p>This is Child {userInfo.name} {userInfo.age}</p>
    </div>
})

// Parent component
function App() {
    console.log('Parent render...')

    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    // Using useMemo to cache data depends on [name] 
    //  When name is updated, {name, age: 18} the cache will be invalidated
    const userInfo = useMemo(() => {
        return { name, age: 18 }
    }, [name])

    return <div>
        <p>
            count is {count}
            <button onClick={() => setCount(count + 1)}>click</button>
        </p>
        <Child userInfo={userInfo}></Child>
    </div>
}

export default App

At this time, the display effect of the browser is as follows:

You can see that the Child component will not be printed unless it is updated for the first time. How is it used? First, we need to wrap the sub components with memo. Then, we use useMemo to cache data. At the same time, the data depends on [name]; It is worth noting that if it does not depend on [name], it will not take effect.

Now let's summarize useMemo as follows:

  • React will update all sub components by default;
  • class components are optimized using shouldComponentUpdate and PureComponent;
  • Hooks uses useMemo for optimization, but the optimization principle is the same as that of class components.

(5)useCallback

We talked about using useMemo to cache data. Now let's talk about useCallback. useCallback is mainly used to cache functions.

Suppose we don't use useCallback now to run the following code:

import React, { useState, memo, useMemo, useCallback } from 'react'

// Sub component, memo is equivalent to PureComponent
const Child = memo(({ userInfo, onChange }) => {
    console.log('Child render...', userInfo)

    return <div>
        <p>This is Child {userInfo.name} {userInfo.age}</p>
        <input onChange={onChange}></input>
    </div>
})

// Parent component
function App() {
    console.log('Parent render...')

    const [count, setCount] = useState(0)
    const [name, setName] = useState('Monday Lab')

    // Cache data with useMemo
    const userInfo = useMemo(() => {
        return { name, age: 21 }
    }, [name])

    function onChange(e) {
        console.log(e.target.value)
    }

    return <div>
        <p>
            count is {count}
            <button onClick={() => setCount(count + 1)}>click</button>
        </p>
        <Child userInfo={userInfo} onChange={onChange}></Child>
    </div>
}

export default App

The printing effect of the browser is as follows:

You can see that if useCallback is not used, it will always print out the sub components, that is, the onChange event will always run. Now, let's transform the onChange event as follows:

// Caching functions with useCallback
const onChange = useCallback(e => {
     console.log(e.target.value)
}, [])

At this point, let's look at the effect of the browser:

As you can see, when useCallback is added and Child is not updated, it will not be updated again!

Next, let's make a summary of useMemo and useCallback:

  • useMemo cache data;
  • useCallback cache function;
  • Both are common optimization strategies of React Hooks.

4. Customize Hook 🗞️

(1) Why use a custom Hook

  • Used to encapsulate general functions;
  • Can develop and use third-party Hooks;
  • Custom Hook brings unlimited scalability and decouples code.

(2) Illustrate with examples

Suppose we want to encapsulate a useAxios now, how do we implement it?

First, we need to install axios in the project. The specific code is as follows:

npm i axios --save

Next, we create a new file in the src|customHooks folder of the project, named useaxios js . The specific codes are as follows:

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

// Encapsulates a custom Hook that axios sends network requests
function useAxios(url) {
    // Whether the loading simulation is currently waiting
    const [loading, setLoading] = useState(false)
    // Data returned when the request is successful
    const [data, setData] = useState()
    // Some information returned when the request fails
    const [error, setError] = useState()

    useEffect(() => {
        // Sending network requests using axios
        setLoading(true)
        axios.get(url) // Send a get request
            .then(res => setData(res))
            .catch(err => setError(err))
            .finally(() => setLoading(false))
    }, [url])

    return [loading, data, error]
}

export default useAxios

Continue. In the src|components folder of the project, create a new file named customhookusage js . This component is mainly used to use the useAxios hook customized above. The specific codes are as follows:

import React from 'react'
import useAxios from '../customHooks/useAxios'

function App() {
    const url = 'http://localhost:3000/'
    // Array deconstruction
    const [loading, data, error] = useAxios(url)

    if (loading) return <div>loading...</div>

    return error
        ? <div>{JSON.stringify(error)}</div>
        : <div>{JSON.stringify(data)}</div>
}

export default App

Finally, we are in the project's app JS to use it. The specific codes are as follows:

import React, { useState } from 'react';
import CustomHookUsage from './components/CustomHookUsage'

function App() {

  return (
    <div>
      {<CustomHookUsage/>}
    </div>
  );
}

export default App;

At this point, let's take a look at the display effect of the browser:

As you can see, first of all, when waiting, it will appear loading first. After that, if the wait ends and the request succeeds, data will be returned. Of course, we do not demonstrate the case of error here. You can change the url to a non-existent request address to test the error.

(3) Summary

After reading the above demonstration code, let's make a summary:

  • Custom hook is essentially a function, starting with use;
  • useState, useEffect or other Hooks can be used internally normally;
  • User defined return results with unlimited formats;

Here we recommend two third-party custom Hook libraries:

  • react-hooks: https://nikgraf.github.io/react-hooks/
  • hooks: https://github.com/umijs/hooks

5. Two important rules of Hooks 🗞️

(1) Hooks usage specification

  • Hooks can only be used in react function components and custom hooks, but not elsewhere. For example, class components and ordinary functions;

  • It can only be used for top-level code, and Hooks cannot be used in loops and judgments;

  • The eslint plugin react hooks plug-in in eslint can help you use react hooks. The configuration is as follows:

    // ESLint profile
    {
        "plugins": [
            // ... Omit line N here
            "react-hooks"
        ],
        "rules": {
            // ... Omit line N here
            "react-hooks/rules-of-hooks": "error", // Check Hook rules
            "react-hooks/exhaustive-deps": "warn" // Check the dependency of effect
        }
    }
    

(2) Hooks call order must be consistent

In react-hooks, calling sequence is a key point that needs special attention. Why?

Let's use a piece of code to demonstrate:

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

function Teach({ couseName }) {
    // Function component, pure function, destroyed upon execution
    // Therefore, no matter component initialization (render) or component update (re render)
    // Will re execute this function to get the latest component
    // This is different from the class component

    // render: initialize the value 'Zhang San' of state
    // Re render: read the state value 'Zhang San'
    const [studentName, setStudentName] = useState('Zhang San')

    // if (couseName) {
    //     const [studentName, setStudentName] = useState('zhang San ')
    // }

    // render: initialize the value 'double crossing' of state
    // Re render: read the value 'double crossover' of state
    const [teacherName, setTeacherName] = useState('Monday Lab')

    // if (couseName) {
    //     useEffect(() => {
    //         //Simulated student check-in
    //         localStorage.setItem('name', studentName)
    //     })
    // }

    // render: add effect function
    // Re render: replace the effect function (the internal function will also be redefined)
    useEffect(() => {
        // Simulated student check-in
        localStorage.setItem('name', studentName)
    })

    // render: add effect function
    // Re render: replace the effect function (the internal function will also be redefined)
    useEffect(() => {
        // The simulation begins
        console.log(`${teacherName} Start class, students ${studentName}`)
    })

    return <div>
        Course:{couseName},
        Lecturer:{teacherName},
        student:{studentName}
    </div>
}

export default Teach

The above code demonstrates a function component. For function components, it is a pure function, which is usually destroyed after execution. Therefore, whether it is component initialization (render) or component update (re render), this function will be re executed to obtain the latest component, which is different from the class component.

Therefore, if we wrap the const [studentName, setStudentName] = useState('zhang San ') above with an IF statement, it will be blocked during execution. At the same time, if you go down in the following order, you may assign the value of Zhang San to teacherName. Therefore, in react hooks, we can't use react hooks in loops and judgments, otherwise it will easily lead to disordered call order.

Based on the above analysis, let's make a summary of this rule:

  • Whether it is render or re render, the Hooks call order must be consistent;
  • If Hooks appear in the cycle and judgment, the order cannot be guaranteed;
  • Hooks depends heavily on the call order! Important!

III ⌨️ Logical reuse of react hooks components

1. Logical reuse of class components

class components have three forms of logical reuse. namely:

  • Mixin
  • High order component HOC
  • Render Prop

Let's talk about their respective shortcomings.

(1)Mixin

  • The source of variable scope is unclear
  • Duplicate attribute name
  • Too many mixins will lead to sequence conflict

(2) High order component HOC

  • There are too many nested component levels, which is difficult to render and debug
  • HOC will hijack props, which must be strictly standardized and prone to omissions

(3)Render Prop

  • Learning costs are high and difficult to understand
  • You can only pass pure functions, which are limited by default

After understanding the disadvantages of the three class components, now let's see how to use Hooks for component logical reuse.

2. Logical reuse of components using Hooks

The essence of using hooks to enable logical reuse of components is to customize hooks. Let's use an example to show.

(1) Examples

First, we create a new file in the src|customHooks folder of the project, named usemouseposition js . The specific codes are as follows:

import { useState, useEffect } from 'react'

function useMousePosition() {
    const [x, setX] = useState(0)
    const [y, setY] = useState(0)

    useEffect(() => {
        function mouseMoveHandler(event) {
            // event.clientX can get the location of the abscissa and ordinate of the mouse
            setX(event.clientX)
            setY(event.clientY)
        }

        // Binding event
        document.body.addEventListener('mousemove', mouseMoveHandler)

        // Unbinding event
        return () => document.body.removeEventListener('mousemove', mouseMoveHandler)
    }, [])

    return [x, y]
}

export default useMousePosition

Continue. In the src|components folder of the project, create a new file named customhookusage js . This component is mainly used to use the logic in useMousePosition above. The specific codes are as follows:

import React from 'react'
import useMousePosition from '../customHooks/useMousePosition'

function App() {

    const [x, y] = useMousePosition()
    return <div style={{ height: '500px', backgroundColor: '#ccc' }}>
        <p>Mouse position {x} {y}</p>
    </div>
}

export default App

Finally, we are in the project's app JS to use it. The specific codes are as follows:

import React, { useState } from 'react';
import CustomHookUsage from './components/CustomHookUsage'

function App() {

  return (
    <div>
      {<CustomHookUsage/>}
    </div>
  );
}

export default App;

At this point, let's take a look at the display effect of the browser:

Using hooks for component logical reuse is much simpler and more flexible than class components. Let's sort out the benefits of hooks for component logic reuse.

(2) Benefits

The benefits of Hooks for component logic reuse:

  • Fully comply with Hooks' original rules, no other requirements, easy to understand and remember;
  • Variable scope is clear;
  • Component nesting will not occur.

IV 📀 React Hooks considerations

1. useState initialization value, valid only for the first time

Let's first look at a piece of code, as follows:

import React, { useState } from 'react'

// Subcomponents
function Child({ userInfo }) {
    // render: initialize state
    // Re render: only the initialized state value will be restored, and no new value will be reset
    //            Can only be modified with setName
    const [ name, setName ] = useState(userInfo.name)

    return <div>
        <p>Child, props name: {userInfo.name}</p>
        <p>Child, state name: {name}</p>
    </div>
}


function App() {
    const [name, setName] = useState('Monday')
    const userInfo = { name }

    return <div>
        <div>
            Parent &nbsp;
            <button onClick={() => setName('Tuesday')}>setName</button>
        </div>
        <Child userInfo={userInfo}/>
    </div>
}

export default App

At this time, the display effect of the browser is as follows:

As you can see, when we click, only UseInfo is changed The value of name, that is, the value of the first Child. At this time, some small partners may have doubts and say, UseInfo The value of name is assigned to name, so why doesn't the second Child change.

The reason is that for useState, we always set an initialization value for state at the beginning. Even if the data is updated, only the first value is valid, and the updated value will not be assigned to it. Therefore, the name value in the second Child below is always Monday.

2. state cannot be modified inside useEffect

Let's simulate a piece of code:

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

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

    // Simulate DidMount
    useEffect(() => {
        console.log('useEffect...', count)

        // Timed task
        const timer = setInterval(() => {
            console.log('setInterval...', count)
            setCount(count + 1)
        }, 1000)

        // Clear scheduled tasks
        return () => clearTimeout(timer)
    }, []) // Dependency is []

    // When the dependency is []: re render will not re execute the effect function
    // When there is no dependency: re render will re execute the effect function

    return <div>count: {count}</div>
}

export default UseEffectChangeState

Now let's look at the effect of the browser:

As you can see, 1234 should be printed incrementally according to our expectations, but it is not displayed in the end. The initial value printed in timer is always 0, that is, this state does not work in useEffect.

Therefore, we need to pay special attention to this in our daily development. It is possible that there are bug s written in the code.

Now that the problem has arisen, we need to think about its solution. The transformation is as follows:

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

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

    // Simulate DidMount
    const countRef = useRef(0)
    useEffect(() => {
        console.log('useEffect...', count)

        // Timed task
        const timer = setInterval(() => {
            console.log('setInterval...', countRef.current)
            // setCount(count + 1)
            setCount(++countRef.current)
        }, 1000)

        // Clear scheduled tasks
        return () => clearTimeout(timer)
    }, []) // Dependency is []

    // When the dependency is []: re render will not re execute the effect function
    // No dependency: re render will re execute the effect function

    return <div>count: {count}</div>
}

export default UseEffectChangeState

The browser displays as follows:

Looking at the above code, we can find that by putting countRef outside useEffect and using useRef, we can modify the value of state to achieve our ultimate goal.

3. useEffect may have a dead loop

Suppose we want to use axios to send network requests and want to add some configuration to the url, then we may handle it as follows:

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

// Encapsulates a custom Hook that axios sends network requests
function useAxios(url, config = {}) {
    const [loading, setLoading] = useState(false)
    const [data, setData] = useState()
    const [error, setError] = useState()

    useEffect(() => {
        // Sending network requests using axios
        setLoading(true)
        axios(url, config) // Send a get request
            .then(res => setData(res))
            .catch(err => setError(err))
            .finally(() => setLoading(false))
    }, [url, config])

    return [loading, data, error]
}

export default useAxios

It seems reasonable, but when config uses the reference data type, that is, config = {} or confgi = [], useEffect will have an endless loop and send requests indefinitely. Therefore, when we want to pass values to axios, we can only pass values of basic data types. The modification code is as follows:

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

// Encapsulates a custom Hook that axios sends network requests
function useAxios(url) {
    const [loading, setLoading] = useState(false
    const [data, setData] = useState()
    const [error, setError] = useState()

    useEffect(() => {
        // Sending network requests using axios
        setLoading(true)
        axios.get(url) // Send a get request
            .then(res => setData(res))
            .catch(err => setError(err))
            .finally(() => setLoading(false))
    }, [url])

    return [loading, data, error]
}

export default useAxios

In this way, there will be no data request sent to the interface in an endless loop.

V 🔍 Conclusion

In the above article, we first understand the basic concept of hooks. Then, we focus on state hook and effect hook. At the same time, we also briefly understand several other hooks. Finally, the contribution of react hooks in component logic reuse and some matters needing attention are discussed.

Here, the introduction of react hooks is over! I wonder if you have some new understanding of react hooks?

🐣 One More Thing

(: recommended in previous periods)

The react column pokes here https://juejin.cn/column/7018019097656950815

(: Ding

  • Pay attention to the official account of Monday's research room. For the first time, you should pay attention to quality articles, and even more, "offer is coming".
  • If you think this article is helpful to you, remember to leave a footprint jio before you go~~ 😉
  • The above is the whole content of this article! See you next time! 👋👋👋

Keywords: Javascript Front-end React TypeScript

Added by mr_griff on Thu, 30 Dec 2021 12:31:36 +0200