preface

Five versions have passed since React16.8 officially released React Hook (this blog post was published in React 16.13.1). I have used Hook for a period of time, and I have to say that I have entered a lot of mistakes in the initial use of Hook. In this article, I will throw out and answer some of the questions I have met, and at the same time sort out my understanding of Hook.

1. Execution flow of Hook

Before explaining what follows, we need to clarify the flow of Hook execution.

When we write Hook Component, the essence is Function Component + Hook, Hook only provides state management and other capabilities. For Function Component, each render will re-execute everything from top to bottom, creating new variables if any.

Let’s look at a simple example

// father.js

import Child from './child';

function Father() {
    const [num, setNum] = useState(0);

  	function handleSetNum() {
        setNum(num+1);
    }
  
    return (
        <div>
            <div onClick={handleSetNum}>num: {num}</div>
            <Child num={num}></Child>
        </div>
    );
}
Copy the code
// child.js

function Child(props) {
    const [childNum, setChildNum] = useState(0);
  
    return (
        <>
            <div>props.num: {props.num}</div>
            <div onClick={()= > {setChildNum(childNum + 1)}}>
                childNum: {childNum}
            </div>
        </>
    );
}
Copy the code

Then let’s look at the execution flow:

Father executes useState, which returns an array, then deconstructs the assignment to num and setNum, and creates handleSetNum, which returns the JSX code to React.

Child receives the data from father, creates the props variable and assigns it, executes useState, deconstructs the assignment to childNum and setChildNum, and returns the JSX code to React.


Next, click on the variable in father that binds the click event to trigger the handleSetNum method and change the Hook State to increment num by one. React rerenders will be triggered because the state changes.

Then let’s look at some of the execution flow for the second rendering:

Father does useState ** again; UseState returns a new array and then deconstructs the assignment to ** newly created num and setNum **; Then create a new function, handleSetNum

Child (props) receives data from father, creates a new variable, and assigns it to props. UseState returns a new array, but since childNum has not changed, the values in the new array are identical to the values in the old array, and the new variables are assigned to childNum and setChildNum


We can verify this with global variables

// father.js

window.selfStates = []; // Create a global variable store
window.selfStates2 = []; // Create a global variable store

function Father() {
    const [num, setNum] = useState([0]); // Change the number to an array
    const [num2, setNum2] = useState([0]); // Set a control group, the control group is initialized, do not modify
    window.selfStates.push(num);
    window.selfStates2.push(num);
  
  	function handleSetNum() {
        setNum([num[0] + 1]) // Do not change the value directly to avoid affecting the old array}... }Copy the code

You can then print it in the browser console and see the result

Of course, the handleSetNum function we created could also be verified in this way.

So, here we have the hook execution flow: every render, every update, the whole thing is updated once, and new variables are created.

2. UseState does not pass an execution function for initialization

Go straight to code

function initState() {
  	console.log('run'); // Execute it once
		return 1;
}

function Father() {
  	const [num, setNum] = useState(initState()); / / ❎
  	const [num, setNum] = useState(initState); / / ✅
}
Copy the code

Because useState is executed once for every update, according to the mechanism of useState, it stores data: if there is no data, initialize it, create new data, and store it; If there is data, the initialization operation is not performed and the value of the data is returned.

However, if we use the form useState(func()), then the func will be executed each time and the value will be passed to useState as an argument, but if it is already initialized, the initialization will be skipped. Every update wastes a run of func function time.

3. Capture value

Hook Component

Take a look at this code

function Father() {
    const [num, setNum] = useState(0);

    function handleSetNum() {
        setNum(num+1);
    }

  	// The num value will be displayed after a delay of 3 seconds
    function handleGetNum() {
        setTimeout((a)= > {
            alert(num);
        }, 3000);
    }

    return (
        <div>
            <div onClick={handleSetNum}>num: {num}</div>
            <div onClick={handleGetNum}>Click me output content</div>
        </div>
    );
}
Copy the code

If num is 0 and handleGetNum is triggered, then handleSetNum is triggered again to change the value of num. What is the output value of num after the 3-second countdown?

Give yourself some space to think about it



































Here are the answers



































The answer is zero

If we use the Class Component to do this, we get the opposite result, with an output value of 1.

In Hook Component, this phenomenon is called Capture Value


Why is this the case?

To answer this question, it’s back to the beginning: Hook execution flow

We know that each render creates a new variable for that render, so in the initial state, I used handleGetNum_0 for the original handleGetNum function, num_0 for the initial num.

We first trigger handleGetNum, then handleSetNum, and then update the data to create a new function handleGetNum_1 and a new variable num_1.

In this process we actually click handleGetNum_0, handleGetNum_0 operates on num_0, so alert is still the value of num_0.

Class Component

So why doesn’t this happen with Class Component?

Let’s see what happens if we use Class Component

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            num: 0
        }
    }

    handleSetNum = (a)= > {
        this.setState({num: this.state.num+1});
    }

    handleGetNum = (a)= > {
        setTimeout((a)= > {
            alert(this.state.num);
        }, 3000);
    }

    render() {
        return (
            <div>
                <div onClick={this.handleSetNum}>num: {this.state.num}</div>
                <div onClick={this.handleGetNum}>Click me output content</div>
            </div>)}}Copy the code

Num. This points to the latest state Value, so there is no Capture Value.

If we want to implement Capture Value in a Class Component, an easy way to do this is to make a closure. This is a simple one and won’t be covered in detail in this article.


What do we do if we don’t want Capture Value to be triggered in the Hook Component?

The answer is useRef

function App() {
    const [num, setNum] = useState(0);
    const nowNum = useRef(0); // Create an additional Ref object because Ref object updates do not trigger react rerendering

    function handleSetNum() {
        setNum(num + 1);
        nowNum.current = num + 1; // Assign nowNum as well as num
    }

    function handleGetNum() {
        setTimeout((a)= > {
            alert(nowNum.current);
        }, 3000); }... }Copy the code

We use nowNum.current just like this.state, because Ref creates an object that points to the latest value.

4. Need to use useCallback and useMemo

UseCallback is similar to useMemo, so let’s put it together.

The difference is that useCallback returns a function, while useMemo returns the value of the function after execution.

The first conclusion

When you need them:

  1. Costly calculations
  2. Avoid pointless rerendering of child components
  3. Data needs to be passed to other components, and the data is objects, functions

When you don’t need them:

  1. Used only within components, there is no passing of data down.
  2. If you want to pass down data, but the value of the data is not an object, not a function


  1. If I have a JSX code that has a value that I need to compute. This calculation is very complicated and costs a lot, but the parameters used in the calculation basically remain the same, so useMemo can save a lot of calculation costs at this time.


  1. If you have a parent component and a child component, every update of the parent component must trigger an update of the child component. Using useMemo to wrap child components prevents meaningless updates of child components.

ex.

function Father() {		
		const [num, setNum] = useState([0]);
    const [num2, setNum2] = useState([0]);

    function handleSetNum() {
        setNum([num[0] + 1]);
    }

    function handleSetNum2() {
        setNum2([num2[0] + 1]);
    }

    return (
        <div>
            <div onClick={handleSetNum}>num: {num[0]}</div>
            <div onClick={handleSetNum2}>num2: {num2[0]}</div>
            <Child num={num}></Child>
        </div>
    );
}
Copy the code

This code does not use useMemo. Child accepts num, and num2 triggers the update of Child. Num2 triggers the update of Father, and causes the update of Child.

Processed code:

function Father() {
    const [num, setNum] = useState([0]);
    const [num2, setNum2] = useState([0]);

    function handleSetNum() {
        setNum([num[0] + 1]);
    }

    function handleSetNum2() {
        setNum2([num2[0] + 1]);
    }

    const ChildHtml = useMemo((a)= > {
      	return <Child num={num}></Child>
    }, [num])

    return (
        <div>
            <div onClick={handleSetNum}>num: {num[0]}</div>
            <div onClick={handleSetNum2}>num2: {num2[0]}</div>
            {ChildHtml}
        </div>
    );
}
Copy the code

The useMemo will store the return value, and when num changes, it will re-execute the function in the first argument to useMemo, return the new value, and save the new value. When num2 changes, the previously saved value is pulled out, thus avoiding rerendering of child components.

Watch who!

Sometimes it’s done this way

// father.js
function Father() {... function func() {};return {
      	<Child func={func} obj={{name: abc, num:2}} ></Child>}}// child.js
function Child(props) {
    useEffect((a)= >{... }, [props])return (
        ...
    );
}
Copy the code

In the subcomponent, you need to simulate componentDidUpdate to handle various parameters and perform some operations when the props changes.

If we have wrapped the child components with useMemo, we have solved a hidden problem. If we are not using useMemo to wrap child components, then be careful

The props passed from Father to Child has two properties, a function, and a custom object

props: {
    func: func,
    obj: {
      	name: abc,
        num: 2}}Copy the code

UseEffect is executed when the props accepted by the child component changes.

This is normal if the value in props. Obj changes, causing useEffect to be executed.

But let’s be clear: a Hook creates a new object every time it executes. That is, if Father creates a new func function and a new obj object every time it is updated, even though we think the func function and the obj object have not changed, the pointer to the variables in props points to the new object and triggers the useEffect that should not have been triggered


Two solutions:

  1. Wrap child components with useMemo.
  2. Wrap func functions and OBj objects with useCallback and useMemo


The memo and the React.PureComponent might be used, but their cache strategy is to compare the values in props using only the uniform characters. So even with these two methods, if you don’t use useCallback and useMemo wrapped around the values passed in props, UseEffect is also triggered in the code above

Tip: Variables obtained through useState do not need to use useMemo, because useState has already processed them to ensure that unupdated value references remain unchanged

const num = useState(0); Num [0] and num[1] references do not change if the specific state value does not change.

const [num, setNum] = useState(0); // In this case, if the value of num does not change, the reference to num and setNum will not change every time it is updated
Copy the code

Tip2: If you pass non-object, non-function content such as number, string, there is no need to wrap.

Congruences compare values for equality, not addresses for equality


There is only a distance between props pass and any non-props pass, as long as objects are used, there may be hidden problems. So be careful who you target!


Performance optimization

Also to be clear, it is often faster to re-execute a piece of code (without useMemo/useCallback) than to store, compare, and evaluate it (with useMemo/useCallback). And in many cases, even with useMemo/useCallback, the optimizations are not noticeable at all (modern browsers and computers are not slow).

So there is no need to use useMemo/useCallback in many cases unless it is for optimization of multi-component nesting or high computation cost

For more information, browse When to useMemo and useCallback

conclusion

  1. The Hook Component re-executes everything from top to bottom every time it renders, creating new variables if any.

  2. UseState (func()) don’t code like this!

  3. Hook Component has Capture Value features

    • You can useuserefTo avoid the Capture value
  4. Precautions for useCallback and useMemo

    When you need them:

    1. Costly calculations
    2. Avoid pointless rerendering of child components
    3. Data needs to be passed to other components, and the data is objects, functions

    When you don’t need them:

    1. Used only within components, there is no passing of data down.
    2. If you want to pass down data, but the value of the data is not an object, not a function
  5. Be careful who you see!!

  6. Sometimes it is faster to re-execute a piece of code than to retrieve a result from the cache


The end of the

If there are any mistakes/deficiencies/improvements/areas that can be optimized in the article, please kindly mention them in the comments. The author will deal with them in the first time after seeing them

If you like this article, please give it a thumbs up at 👍 and github’s star ⭐ is supporting the author’s ongoing work at ❤️️


The relevant data

Kentcdodds.com/blog/usemem…

Overreacted. IO/useful – Hans/how…

Zhuanlan.zhihu.com/p/85969406?…