preface

Remember that I haven’t written this article for a while. Here are eight React Hooks that I think I should learn. There should be nine if you include custom Hooks, but I won’t expand on them in this article. I just learned the React framework before I came to netease. After reading the company’s project for one day, I found that I had to read React Hooks. After watching it for a weekend, I made some summaries and shared them here. This article covers each of these Hooks with examples and use scenarios. It is a bit long, so you can look at them purposefully for the Hooks you want to learn.

React Hooks

1 useState

In class 1.1 components

In a class component, you can use this.state to define the state of the class component, as shown in the following code

import React from 'react'

class StateClass extends React.Component{
    constructor(){
        super(a)this.state = {
            name: 'class'}}render() {
        return (
            <div onClick={ this.setName} >This is a class component ————{this.state.name}</div>
        )
    }

    setName = () = > {
        this.setState({
            name: 'I'm doing this with the class component method'}}})export default StateClass
Copy the code

1.2 Function Components

In a function component, you can use useState to define the state of the function component. Use useState to create the state

  • 1. The introduction of
  • 2. Accept a parameter as an initial value
  • 3. Return an array with the state as the first value and the state-changing function as the second value

Looking at the same function implementation, the useState code implementation of the function component is as follows. Is it easier to write useState for the same function? Let’s move on to another set of hooks

import React,{ useState } from 'react'

function StateFunction () {
    const [name, setName] = useState('function')
    // Class name, modify function name initial value

    return (
        <div onClick={() = >}> // setName can also write methods, such as setName(val => val+' XXXX ') which is a functional component ————{name}</div>)}export default StateFunction
Copy the code

2 useEffect

2.1 Introduction

UseEffect is also called hooks. What it does: Adds an end-of-render signal to a component that has no life cycle. Execution timing: Executed after rendering

  • What are side effects?

    • Side effects: Data retrieval, data subscription, and manual changes to the DOM in the React component are all side effects
    • Because the page we render is static, anything that happens after it has an effect on it is called a side effect
  • Use:

    • 1. The first argument takes a function as an argument
    • 2. The second argument, receiving the dependency list, will execute the function only if the dependency is updated
    • 3. Return a function, first execute the return function, then execute the parameter function

2.2 The case where the second parameter is not accepted

If the second parameter is not accepted, the useEffect callback will be called after the first render and every time the render page is updated, so you need to consider the scenario.

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

function StateFunction () {
    const [num, setNum] = useState(0)
    
    useEffect( () = > {
        console.log('2222 Functional component end render ')})return (
        <div onClick={() = >SetNum (num => num+1)}> This is a functional component ————{num}</div>)}Copy the code

2.3 When the second parameter is accepted

useEffect( () = > {
    console.log('2222 Functional component end render ')
},[])
// change the second argument to useEffect
Copy the code

Here, we can pass the second argument an array that represents the list on which the update will be executed. The callback will only be triggered when the list of dependencies changes (useEffect is re-executed when any item in the array changes)

  • An empty array is passed in[], then telluseEffectDon’t depend onstate,propsAny value of,useEffectIt will only run once, and the common scenario is that the method of getting data for the page can be written here and called to get the initial data for the page
  • Pass an array of values, or an array of values, as in[num],[num,val], the above code is changed to the following. The callback will only be triggered again if any of the values in the array change
    useEffect( () = > {
        console.log('2222 Functional component end render ')
    },[num])
    
    useEffect( () = > {
        console.log('2222 Functional component end render ')
    },[num,val])
    // change the second argument to useEffect
    Copy the code

2.4 Side effects of removal

These are all side effects that don’t need to be cleaned up. Callbacks trigger simple methods, but there are some side effects that need to be cleaned up. Such as binding DOM events, in which case cleaning is important to prevent memory leaks, such as the code comparison shown below

  • 1.Without removing the side effects. Click normal output for the first timePrint current location“And every timeuseEffectThe call is bound to a new oneupdateMouseMethod, then click on the binding triggered by more and more methods, then click on a crazy printPrint current location, which leads to page performance, memory leaks, etc
    const [positions,setPositions] = useState({ x:0.y:0 })
    
    useEffect( () = > {
        console.log('2222 Functional component end render ')
    
        const updateMouse = (e) = > {
            console.log('Print current location')
            setPositions({ x:e.clientX, y:e.clientY })
        }
        document.addEventListener('click',updateMouse)
    })
    
    return (
        <div>
            <p>x:{ positions.x }</p>
            <p>y:{ positions.y }</p>
        </div>
    )
    Copy the code
  • 2.Remove side effects in case(Modify only part of the code, the other code as above). Example code
    • The first refresh or entry to the page executes something other than return, that is, a bound method is executed, and then the updateMouse method is bound to the Click event

    • UseEffect is cleared and returned, but the contents of return are not executed.

    • Then when you click the first time, it will print the current mouse page coordinates, and then execute the previous return. Note that this is the clear event binding method of the previous return, and then execute the clear event binding method of the previous useEffect

    • It then executes the new useEffect binding method and returns the useEffect cleanup method again, thus creating a chain of events

    • When the page unloads, the last return is returned to clear the event binding, which ensures that the DOM event method added by the binding is removed when the page unloads

    • (The above written execution process is not analyzed from the principle, just a simple description. It might be a little confusing, but if you don’t understand it, watch it a few more times and run the sample code to make sense.)

    useEffect( () = > {
        console.log('2222 Functional component end render ')
        const updateMouse = (e) = > {
            console.log('Print current location')
            setPositions({ x:e.clientX, y:e.clientY })
        }
        document.addEventListener('click',updateMouse) // Add a binding method event (to modify dependencies, bind to dependencies)
    
        return () = > {
            // useEffect is executed before the last return
            document.removeEventListener('click',updateMouse)
            // Remove the binding method event (to modify the dependency, bind to the dependency)
            console.log('1111 destroyed')}})Copy the code

2.5 useEffect Asynchronism

Each effect function belongs to a specific render:

  • ①useEffect scheduling does not block browser update screen
  • (2) Every time you re-render, a new effect will be generated, replacing the previous one to ensure that the value obtained in Effect is up to date and does not have to worry about expiration. Set as follows3000Three clicks in a row in milliseconds will print a total of four times, respectivelyZero, one, two, three.0It’s triggered automatically after the first rendering, and the restOne, two, threeIt’s three clicks per triggercountvalue
    function Counter() {
      const [count, setCount] = useState(0);
    
      useEffect(() = > {
        setTimeout(() = > {
          console.log(`${count}`);
        }, 3000);
      });
    
      return (
        <div>
          <p>You hit {count} times</p>
          <button onClick={()= >SetCount (count + 1)}> Click me</button>
        </div>
      );
    }
    Copy the code
    • Compare with a class component: If placed in a class component, it is the final value changed within the time set for printing. The equivalent code in a class component is as follows (only the key code is given). What does it mean to change the final value of the print setting over time? If you set it to 3000 milliseconds, then the moment the render ends is timed, and three clicks in a row within 3000 milliseconds will result in 4 prints3If I wait for the first timecomponentDidMountWhen the timer set in, and then suddenly click three times in a row within 3000 milliseconds, the first one will be printed first0Print it three more times3.Because it’s the same thing in the class notationnumThe status value. If you set the time to0Millisecond, so actually if you click three times in a rowuseEffectOnce again, print it first0“, and then printOne, two, threeBecause this change is quick and you won’t feel the difference, you can try it yourself.
      this.state = {
        	num:0
      }
      
      componentDidMount(){
          setTimeout(() = > {
              console.log(this.state.num)
          },3000)}componentDidUpdate(){
          setTimeout(() = > {
              console.log(this.state.num)
          },3000)}render() {
          return (
              <div onClick={ this.setNum} >This is a class component ————{this.state.num}</div>
          )
      }
      
      setNum = () = > {
          this.setState({
              num: this.state.num+1})}Copy the code

3 useLayoutEffect

UseLayoutEffect is commonly referred to as a side effect of DOM manipulation hooks. The function is to perform an operation after a DOM update is complete. Execution timing: Execute after DOM update

Compared with useEffect

  • The same
    • 1. The first argument takes a function as an argument
    • 2. The second argument, receiving the dependency list, will execute the function only if the dependency is updated
    • 3. Return a function, first execute the return function, then execute the parameter function
    • (So the execution process is the same)
  • The difference between
    • Execution timing varies.useLayoutEffectinDOMExecute after update;useEffectinrenderExecute after rendering. You’ll see by executing the sample codeuseLayoutEffectThan everuseEffectExecute first. That’s becauseDOMAfter the update, the rendering will finish or will finish
const [num, setNum] = useState(0)
// Implement the componentWillMount life cycle in the class component
useLayoutEffect( () = > {
    console.log('useLayoutEfffect')
	// Event binding can also be done here
    return () = > {
    	// Event binding removal can also be done here
        console.log(1)
    }
},[num])

useEffect( () = > {
    console.log('useEffect')
},[num])

return (
    <div onClick={() = >SetNum (num => num+1)}> This is a functional component ————{num}</div>
)
Copy the code

4 useMemo

Using useMemo, you can pass a create function and a dependency. The create function returns a value, and only when the dependency changes will the function be called again to return a new value. Simply put, the effect is to make the functions in the component follow state updates (that is, function functions in the optimization function component).

  • Use:
    • 1. Accept a function as an argument
    • 2. Also accept the second parameter as the dependency list (can be compared with useEffect and useLayoutEffect)
    • 3. A value is returned. The return value can be any function, object, etc

4.1 Optimization scenarios of complex Computing Logic

  • The code before optimization is as follows. When we clickdivRegion is triggered at this timesetAgeWhat has changed is thatage, withgetDoubleNumThe method is actually irrelevant, but if you look at the console, you can see that it prints multiple timesGet double Num, indicating that the method is constantly fired, which is not necessary. If there is a large amount of calculation in the method, it will have a certain impact on performance, so it needs to be optimized
    const [num, setNum] = useState(1)
    const [age, setAge] = useState(18)
    
    function getDoubleNum () {
        console.log('gets double Num${num}`)
        return 2 * num  // Assume complex computing logic
    }
    
    return (
      <div onClick={() = > { setAge( age => age+1 )} }>
          <br></br>This is a functional component ————{getDoubleNum()}<br></br>The age value is ————{age}<br></br>
      </div>
    )
    Copy the code
  • useuseMemoThe optimized code is as follows. At this timegetDoubleNumThe method is to receive a value that is returned, so notice in the comments that the parentheses are removed. useuseMemoAfter, click againdivRegional changeageIs returned when executedreturn 2*numAs well as printing only innumUpdate, and return the value togetDoubleNumRendering to the view reduces unnecessary computation and optimizes
    const [num, setNum] = useState(1)
    const [age, setAge] = useState(18)
    
    const getDoubleNum = useMemo( () = > {
        console.log('gets double Num${num}`)
        return 2 * num  // Assume complex computing logic
    },[num] )
    
    return (
        <div onClick={() = > { setAge( age => age+1 ) }  }>
            <br></br>This is a functional component ————num: {getDoubleNum} // Note that there are no parentheses because this is a return value<br></br>The age value is ————{age}<br></br>
        </div>
    )
    Copy the code

4.2 The problem of repeated rendering of parent and child components to optimize the use of the scene

  • The code before optimization is as follows. The child component wraps onememo, but the package will still be re-rendered, why? Because we defined itinfoisconstA local variable is defined, and a new one is redefined each time it is rerenderedinfoAnd then when the sub-components perform shallow comparisons,infoIt will always be different, so it will be re-rendered (you can follow the example by clicking the button, and you will see that the child component keeps printingI'm a child component). If the subcomponents are complex, this can have an impact on page performance
    const Child = memo( () = > {
        console.log('I'm a child component')
        return <p>I'm a child component</p>
    })
    
    function Parent() {
        const [show,setShow] = useState(true)
    
        const info = {
            name: 'Even'.age: 22
        }
    
        return(
            <div>
                <Child info={ info} / >
                <button onClick={() = >setShow(! }> Click to update the status</button>
            </div>)}Copy the code
  • useuseMemoThe following code is shown (only the modified code is given, the other code is the same as the above example). Optimized this way, the child component will only render once in the initialization state when we click the button becauseinfoThe parceluseMemoThe dependency does not change and the return value is the same, so there is no re-rendering of the child component.
    const info = useMemo( () = > {
        return {
            name: 'Even'.age: 22
        }
    },[])
    Copy the code

5 useCallback

A similar one is called useCallback, which allows certain operations and methods to be executed following state updates.

Contrast with useMemo.

  • And the simple way to think about it is,useMemo(() => Fn,deps)The equivalent ofuseCallback(Fn,deps)

Difference:

  • useCallbackIs optimized for the callback function passed, and returns a function;useMemoThe return value can be anything, function, object, etc

Similarities:

  • In terms of the method of use,useMemowithuseCallbackThe same. Take a function as an argument, and also take a second argument as a dependency list

5.1 why saiduseCallbackCaching is a function (important difference)

  • UseCallback, while similar to useMemo, returns and caches a function, as compared to the following example code. ①②③ Comparison of the three cases (you can copy the code and comment the code comparison respectively)

    • In the ① case, it will print only once to get double Num1, i.e. the first rendering, and then click on the div area to change the age value, so it will not execute. Since getDoubleNum already gets the value returned by the function passed in useMemo, it caches it

    • In the case of ②, the first rendering will print for a double Num1, and then each click will print for a double Num1. Why? Doesn’t useCallback also have caching capabilities? This is because, as we mentioned earlier, useCallback returns a function. Because the useCallback function is defined in the current component, the component is re-rendered, and it will also be re-rendered. If the dependence of ③ is [], then you will understand. Therefore, scenarios with complex computational logic are not suitable for caching using useCallback because the incoming function content is constantly being executed.

    • In the case of ③, we combine the marking code at ②③, and set can only store a unique value. We observe the length of the printed set

      • whenuseCallbackRely on is empty[]“, we click multiple times in a rowdivRegion, althoughuseCallbackThe content is executed continuously, but we can see it printed outsetThe length of omega is always omega2That’s because it keeps adding the same functionset, sosetThe length of phi is constant
      • And when theuseCallbackRely on for[num]“, we click multiple times in a rowdivArea, you can see it printed outsetIt keeps adding up,One, two, three, four, five, six.... becausenumIs changing, so every time the cached function is a new function, so add insetThe functions of phi are different, sosetThe length of the dot is added again and again
    const set = new Set(a)export default function StateFunction () {
        const [num, setNum] = useState(1)
        const [age, setAge] = useState(18)
    
        const getDoubleNum = useMemo( () = > {
            console.log('gets double Num${num}`)
            return 2 * num  //
        },[] )
    
        const getDoubleNum = useCallback( () = > {
            console.log('gets double Num${num}`)
            return 2 * num  // 2
        },[] )
    
        set.add(getDoubleNum())  // Set the dependency of Callback to [] and [num].
        console.log('set. The size:,set.size)
    
        return (
            <div onClick={() = > { setNum( num => num+1 ) }  }>
                <br></br>This is a functional component ————num: {getDoubleNum} //① In the useMemo case this is a functional component ————num: {getDoubleNum()} //② in the useCallback case<br></br>The age value is ————{age}<br></br>
            </div>)}Copy the code

5.2 Application Scenario of the useCallback

It is possible to optimize the issue of parent-child component parameter rendering. Simply put, if the parent’s passed function is not updated, the child’s function will not be reexecuted

  • In general, when the parent component is updated, the child component is also updated. But if what the parent passes to the child doesn’t change, then some of the child’s actions (actions that need to be synchronized with changes in the incoming content) don’t need to be performed, which can affect page performance, so we can optimize this situation.

  • For example, we pass getDoubleNum to the child component, click on the div field to change the value of num, we use the parent component useCallback with the child component useEffect to optimize. Only when the parent component’s num changes and the getDoubleNum passed in to the child component changes will we perform some of the child component’s operations that need to be updated (i.e., comment the code at the annotation point), so as to avoid repeated unnecessary update operations of the child component that affect page performance

function Parent () {

    const [num, setNum] = useState(1)
    const [age, setAge] = useState(18)

    const getDoubleNum = useCallback( () = > {
        console.log('gets double Num${num}`)
        return 2 * num
    },[num] )

    return (
        <div onClick={() = >{setNum(num => num+1)}}> This is a functional component ————num:{getDoubleNum()}<br></br>The age value is ————age:{age}<br></br>
            set.size:{set.size}
            <Child callback={ getDoubleNum() }></Child>
        </div>)}function Child(props) {
    useEffect( () = > {
        console.log('Callback updated') // This represents operations that need to be synchronized with changes in the incoming content
    },[props.callback])

    return (
        <div>Subcomponent getDoubleNum{props. Callback}</div>)}Copy the code

5.3 Summarize and refer to the article in detail

Simple summary of use scenario judgment:

  • Only in cases where the child component does not need the parent component’s values and functionsmemoThe function wraps the child component
  • If a function is passed to a child component, useuseCallback
  • Caches when complex computational logic within a component needs to return valuesuseMemo
  • If a value is passed to a child component, useuseMemo

Refer to the article:

  • The above is my personal understanding based on the reference materials. If you think what I said is still not clear, you can refer to these two well-written articles, perhaps it will be clearer
  • UseMemo and useCallback Usage Guide
  • UseCallback & useMemo

6 useRef

Simply put, useRef returns an index of child elements that remains constant throughout the life cycle. That’s what it does: Keep data for a long time. Note That the saved object is changed without notice. Property changes are not re-rendered

  • Don’t useuseRefIf we had a requirement that a timer should be cleared when its increment reaches a limit, we would use the following code. At this point, the following code is actually unable to complete the given requirements, whennumIs greater than10After, will find non-stop printingOver 10, clear timerIn fact, the timer is not cleared, so it will always execute the two printed content, but will find the printed contenttimerAccording toundefinedWhy is that? Because we’re passing every time we rendersetIntervalreturnabletimer.timerIt’s updated, and it’s losttimerThis data makes it impossible to clear a timer that needs to be cleared
    const [num, setNum] = useState(0)
    
    let timer
    useEffect( () = > {
        timer = setInterval( () = > {
            setNum( num= > num+1)},400 )
    },[] )
    
    useEffect( () = > {
        if(num > 10) {console.log('Greater than 10, clear timer')
            console.log('the timer:,timer)
            // Each timer is independently render, so we can't get it
            clearTimeout(timer)
        }
    },[num] )
    
    return (
        <div>This is a functional component ————num:{num}</div>
    )
    Copy the code
  • useuseRefAfter, the code is as follows. We can see thatnumSince the toAfter 11I printed it onceOver 10, clear timerAs well asref.current 1, and then it stops incrementing because the timer is cleared.refIs an object,ref.currentStores the entire life cycle of the timeridValue, so when the timer is cleared, the timer can be cleared exactly
    • Save a value that remains constant throughout its life cycle
    const [num, setNum] = useState(0)
    
    const ref = useRef()
    useEffect( () = > {
        ref.current = setInterval( () = > {
            setNum( num= > num+1)},400 )
        // ref.current = '111'
    },[] )
    
    useEffect( () = > {
        if(num > 10) {console.log('Greater than 10, clear timer')
            console.log('ref.current',ref.current)
            clearTimeout(ref.current)
        }
    },[num] )
    
    return (
        <div>This is a functional component ————num:{num}</div>
    )
    Copy the code
    • To assign a valueref.currentPage re-rendering is not actively triggered. When we change the code to look like this, we print the discovery on the consoleref.currentIs printed as111, but the page view is still empty becauserefChanges to saved objects are not actively notified, and property changes are not re-rendered
    const [num, setNum] = useState(0)
    
    const ref = useRef()
    useEffect( () = > {
        ref.current = '111'
        console.log('ref.current',ref.current)
    },[] )
    
    return (
        <div>This is the value of ref. Current -- ref. Current :{ref. Current}<br></br>This is a functional component ————num:{num}</div>
    )
    Copy the code

7 useContext

UseContext allows state passed in by the parent to be shared between child components. Function is colloquially referred to as wandering with child components.

  • Don’t useuseContext, we have the following scenario where our parent component has a value passed to a different child component. The code shown in the example is2But what if I need to add too many children? You can’t always add and write one by one, and if the same variable name is passed in and it changes, you have to change it one by one, so we can useuseContextOptimize your code
    function StateFunction () {
        const [num, setNum] = useState(1)
    
        return (
            <div>
                <button onClick={() = >SetNum (num => num+1)}> Increment the value of num+1</button>
                <br></br>This is a functional component -- num:{num}<Item1 num={num}></Item1>
                <Item2 num={num}></Item2>/ /...</div>)}function Item1 (props) {
        return (
            <div>Subcomponent 1 num: {props. Num}</div>)}function Item2 (props) {
        return (
            <div>Subcomponent 2 num: {props. Num}</div>)}Copy the code
  • useuseContextAfter optimization, the code looks like this so that we only need to use it in child componentsHandle useContext (Context)To obtain the data, add the same type of child components do not need to pay attention to the parent component of the subcomponent definitionpropsPass in the value as follows
    • Need to introduceuseContetx.createContextTwo content
    • throughcreateContextCreate a context handle
    • Context.ProviderTo determine the scope of data sharing
    • throughvalueTo distribute content
    • In the child component, passHandle useContext (Context)To get the data
    • Matters needing attentionThe upper level data changes, which will definitely trigger rerendering (clickbuttonButton triggers the parent component to update the incomingnumValue to see child components re-rendered)
    const Context = createContext(null)
    
    function StateFunction () {
        const [num, setNum] = useState(1)
    
        return (
            <div>
                <button onClick={() = >SetNum (num => num+1)}> Increment the value of num+1</button>
                <br></br>This is a functional component -- num:{num}<Context.Provider value={num}>
                    <Item3></Item3>
                    <Item4></Item4>
                </Context.Provider>
            </div>)}function Item3 () {
        const num = useContext(Context)
    
        return (
            <div>Subcomponent 3: {num}</div>)}function Item4 () {
        const num = useContext(Context)
    
        return (
            <div>Subcomponent 4: {num+2}</div>)}Copy the code

8 useReducer

Redux can now be used in functional components using useReducer instead of class components. The purpose is to obtain the desired state from the state management tool.

  • How to useuseReducer.ReduxThe must-have is the warehousestoreAnd managersreducer. whileuseReducerAgain, you need to create a data warehousestoreAnd managersreducer, in the sample code comments. And then we can go through1.Defines an array of actions to get the state and change the state, passed in when the action is triggeredtypeType judgments are triggeredreducerWhich action, and then modify the data. The important thing to note is that inreducerIn thereturnObject that needs to be converted tostateDeconstruct, otherwise there’s only one state leftnumThe value of
    const store = {
        age:18.num:1
    }	// Data warehouse
    
    const reducer = (state, action) = > {
        switch(action.type){
            case 'add':
                return {
                    ...state,
                    num: action.num+1
                }
    
            default:
                return {
                    ...state
                }
        }
    } / / manager
    
    function StateFunction () {
        const [state,dispacth] = useReducer(reducer,store)  / / 1.
    
        return (
            <div>
                <button onClick={() = >{dispacth({type: 'add', num: state.num})}}> Increment the value of num +1</button>
                <br></br>This is a functional component -- num:{state.num}</div>)}Copy the code

conclusion

Are you done? I don’t know if you have any gain after reading this article is analyzed from the use level and some daily application examples, and there is no deep analysis. If you want to learn more about these Hooks, go to the official source code. Learn from React Hooks learn from React Hooks

At the end of the article

If you think my writing is good, you can give me a thumbs up. If there is anything wrong or bad, please comment and point it out so that I can correct it