This article is available on Github: github.com/beichensky/… Welcome Star!

Q&A

  • 🐤 What are the obvious differences between useState and setState?

  • 🐤 useState and useReducer initial value If it is a return value of an execution function, will the execution function be executed more than once?

  • 🐤 restore the initial value of useReducer, why not restore back?

  • 🐤 useEffect How to model componentDidMount, componentUpdate, componentWillUnmount life cycle?

  • 🐤 How to set event listeners for DOM correctly in useEffect?

  • 🐤 useEffect, useCallback, and props are old values.

  • 🐤 useEffect why is there an infinite execution problem?

  • 🐤 useEffect race

  • 🐤 How do I save some properties in a function component and follow the component through creation and destruction?

  • 🐤 How can I optimize when useCallback triggers frequently?

  • 🐤 What is the difference between useCallback and useMemo?

  • 🐤 Should useCallback and useMemo be used frequently?

  • 🐤 How do I call a child’s state or method from a parent?

I believe that after reading this article, you can get the answers you need.

First, function component rendering process

Let’s take a look at how function components work:

Counter.js

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

    return <p onClick={()= > setCount(count + 1)}>count: {count}</p>;
}
Copy the code

Each time you click on the p tag, count + 1, setCount triggers the rendering of the function component. A re-rendering of a function component is actually a re-execution of the current function. In each rendering of a function component, the internal state, the function, and the props passed in are independent.

Such as:

// First render
function Counter() {
    // First render, count = 0
    const [count, setCount] = useState(0);

    return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}


// Click the p TAB to trigger a second rendering
function Counter() {
    // Second render, count = 1
    const [count, setCount] = useState(0);

    return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}

// Click the p TAB to trigger a third render
function Counter() {
    // Render the third time, count = 2
    const [count, setCount] = useState(0);

    return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}

// ...
Copy the code

The methods declared in function components are similar. Therefore, each frame rendered by the function component corresponds to its own independent state, function, and props.

UseState/useReducer

useState VS setState

  • UseState applies only to function components and setState applies only to class components

  • UseState can declare multiple values in a function component, and state values in a class component must be declared in the state object of this

  • In general, when state changes:

    • When useState changes state, the values of the same useState declaration are overwritten, and multiple useState declarations trigger multiple renders

    • SetState When state is changed, objects with multiple setStates are merged

  • When useState changes state and sets the same value, function components do not re-render, whereas class components that inherit Component trigger rendering even with the same value of setState

useState VS useReducer

The initial value

  • useStateIf the initial value is a value, you can directly set it. If it is a function return value, you are advised to set it using a callback function
const initCount = c= > {
    console.log('initCount execution');
    return c * 2;
};

function Counter() {
    const [count, setCount] = useState(initCount(0));

    return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}

Copy the code

You’ll notice that even though the Counter component doesn’t reinitialize count when it rerenders, the initCount function repeats itself

Change to a callback function:

const initCount = c= > {
    console.log('initCount execution');
    return c * 2;
};

function Counter() {
    const [count, setCount] = useState(() = > initCount(0));

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

At this point, initCount is only executed when the Counter component is initialized, and then no matter how the component is rendered, initCount is never executed again. Okay

  • useReducerWhen setting an initial value, it must be a value, not a callback function
    • If an executor returns the value, the executor will still execute when the component is re-rendered

Modify the state of

  • useStateThe value is the same when changing the statususeStateThe declared state is overwritten
function Counter() {
    const [count, setCount] = useState(0);

    return (
        <p
            onClick={()= > {
                setCount(count + 1);
                setCount(count + 2);
            }}
        >
            clicked {count} times
        </p>
    );
}
Copy the code

The step of count in the current interface is 2

  • useReducerChange the status several timesdispatchThe components are rendered in sequence
function Counter() {
    const [count, dispatch] = useReducer((x, payload) = > x + payload, 0);

    return (
        <p
            onClick={()= > {
                dispatch(1);
                dispatch(2);
            }}
        >
            clicked {count} times
        </p>
    );
}
Copy the code

The step of count in the current interface is 3

reductionuseReducerThe initial value of, why can not restore

Take this example:

const initPerson = { name: 'Ming' };

const reducer = function (state, action) {
    switch (action.type) {
        case 'CHANGE':
            state.name = action.payload;
            return { ...state };
        case 'RESET':
            return initPerson;
        default:
            returnstate; }};function Counter() {
    const [person, dispatch] = useReducer(reducer, initPerson);
    const [value, setValue] = useState('little red');

    const handleChange = useCallback(e= > setValue(e.target.value), []);

    const handleChangeClick = useCallback(() = > dispatch({ type: 'CHANGE'.payload: value }), [value]);

    const handleResetClick = useCallback(() = > dispatch({ type: 'RESET' }), []);

    return (
        <>
            <p>name: {person.name}</p>
            <input type="text" value={value} onChange={handleChange} />
            <br />
            <br />
            <button onClick={handleChangeClick}>Modify the</button>| {'}<button onClick={handleResetClick}>reset</button>
        </>
    );
}
Copy the code

Click the Modify button to change the name of the object to little red, click the reset button to restore the original object. But let’s look at the effect:

You can see that after name changes the red, no matter how you click the reset button, it cannot be restored.

This is because we changed the state property of initPerson, causing the initial value of initPerson to change. Therefore, after RESET, even if initPerson ‘ ‘is returned, the name value is still red.

Therefore, when we modify the data, we should pay attention to not perform attribute operation on the original data, but create a new object for operation. For example, make the following changes:

// ...

const reducer = function (state, action) {
    switch (action.type) {
        case 'CHANGE':
            / /! The modified code
            constnewState = { ... state,name: action.payload }
            return newState;
        case 'RESET':
            return initPerson;
        default:
            returnstate; }};// ...
Copy the code

Check the effect after modification, can be reset normally:

Third, useEffect

useEffect

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

    useEffect(() = > {
        console.log('count: ', count);
    });

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

Every time you click on the P TAB, the Counter component is rerendered and you can see the log printed on the console.

useuseEffectsimulationcomponentDidMount

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

    useEffect(() = > {
        console.log('count: ', count);
        // Set the dependency to an empty array} []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

Set the useEffect dependency to an empty array, and you can see that the console prints out only when the component is first rendered. No matter how the count is updated, it will not print again.

useuseEffectsimulationcomponentDidUpdate

  • Use conditions to determine if a dependency is an initial value, or follow update logic if it is not
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() = > {
        if(count ! = =0) {
          console.log('count: ', count);
        }
    }, [count]);

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

The downside of this approach, however, is that multiple comparisons are required when there are multiple dependencies, so you can choose to use the following approach.

  • useuseRefSet an initial value for comparison
function Counter() {
    const [count, setCount] = useState(0);
    const firstRender = useRef(true);

    useEffect(() = > {
        if (firstRender.current) {
          firstRender.current = false;
        } else {
          console.log('count: ', count);
        }
    }, [count]);

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

useuseEffectsimulationcomponentWillUnmount

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

    useEffect(() = > {
        console.log('count: ', count);

        return () = > {
          console.log('component will unmount')}}, []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

UseEffect returns a function from the wrap function that triggers execution when the function component is re-rendered, cleaning up the previous frame of data. So this function can do some cleaning. If useEffect is given a dependency that is an empty array, then when the return function is executed, the component is actually unloaded.

Set useEffect to an empty array of dependencies and return a function that is equivalent to componentWillUnmount

Note that you must set the dependency to an empty array. If it is not an empty array, this function is not fired when the component is unloaded, but when the component is re-rendered, cleaning up the data from the previous frame.

inuseEffectThe right toDOMSetting up Event Listeners

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

    useEffect(() = > {
        const handleClick = function() {
            console.log('count: ', count);
        }
        window.addEventListener('click', handleClick, false)

        return () = > {
          window.removeEventListener('click', handleClick, false)}; }, [count]);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

In useEffect, set the event listener. In return, clean up the side effects and cancel the event listener

inUseEffect, useCallback, useMemoObtained fromThe state, props,Why old value

As we said earlier, each frame of a function component has its own independent state, function, and props. UseEffect, useCallback, and useMemo have the caching function.

Therefore, we take the variables under the current scope of the corresponding function. If the dependencies are not set correctly, useEffect, useCallback, and useMemo are not re-executed, using the same values as before.

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

    useEffect(() = > {
      const handleClick = function() {
        console.log('count: ', count);
      }
        window.addEventListener('click', handleClick, false)

        return () = > {
          window.removeEventListener('click', handleClick, false)}; } []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

Again, if you set useEffect to an empty array as a dependency, no matter how many times the count changes, if you click on window, the printed count will still be 0

useEffectWhy does infinite execution occur in

  • Not foruseEffectSet the dependency and in theuseEffectIn the updatestate, will cause the interface to repeatedly render
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() = > {
      setCount(count + 1);
    });

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

This would cause the interface to repeat rendering indefinitely because there are no dependencies set. If we wanted to set count to a new value when the interface first renders, we would just set the dependency to an empty array.

Changed: Only the count value is set during initialization

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

    useEffect(() = > {
      setCount(count + 1); } []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

The above example is a problem when a dependency is missing, and a problem when a dependency is properly set.

  • There is one requirement at this point: every timecountWhen adding, we need to turn the page (page+ 1), see how to write:

Because we’re relying on count, we need to include count in the dependency, and we need to rely on page to modify the page, we need to include page in the dependency as well

function Counter() {
    const [count, setCount] = useState(0);
    const [page, setPage] = useState(0);

    useEffect(() = > {
        setPage(page + 1);
    }, [count, page]);

    return (
        <>
            <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>
            <p>page: {page}</p>
        </>
    );
}
Copy the code

This will also cause the interface to repeatedly render the situation, then change the page to function mode, and remove the page from the dependency

Modified: can achieve the effect, and avoid repeated rendering

function Counter() {
    const [count, setCount] = useState(0);
    const [page, setPage] = useState(0);

    useEffect(() = > {
        setPage(p= > p + 1);
    }, [count]);

    return (
        <>
            <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>
            <p>page: {page}</p>
        </>
    );
}
Copy the code

Fourth, race

Executing earlier but returning later incorrectly overwrites the status value

In useEffect, there may be a scenario of network request. We will initiate a network request according to the id passed in by the parent component. When the id changes, the request will be renewed.

function App() {
    const [id, setId] = useState(0);

    useEffect(() = > {
        setId(10); } []);// Pass the ID attribute
    return <Counter id={id} />;
}


// Simulate a network request
const fetchData = id= >
    new Promise(resolve= > {
        setTimeout(() = > {
            const result = ` id for${id}Request result ';
            resolve(result);
        }, Math.random() * 1000 + 1000);
    });


function Counter({ id }) {
    const [data, setData] = useState('In the request... ');

    useEffect(() = > {
        // Send a network request to modify the interface display information
        const getData = async() = > {const result = await fetchData(id);
            setData(result);
        };
        getData();
    }, [id]);

    return <p>result: {data}</p>;
}
Copy the code

Show results:

In the example above, refreshing the page several times, you can see that the end result sometimes displays the result of a request with ID 0, and sometimes it displays the result with ID 10. The correct result should be ‘request result with ID 10’. That’s the problem with races.

Solutions:

  • Canceling asynchronous operations
// Map requested by the storage network
const fetchMap = new Map(a);// Simulate a network request
const fetchData = id= >
    new Promise(resolve= > {
        const timer = setTimeout(() = > {
            const result = ` id for${id}Request result ';
            // End of request Remove the corresponding ID
            fetchMap.delete(id);
            resolve(result);
        }, Math.random() * 1000 + 1000);

        // Set the ID to fetchMap
        fetchMap.set(id, timer);
    });

// Cancel the network request corresponding to id
const removeFetch = (id) = > {
  clearTimeout(fetchMap.get(id));
}

function Counter({ id }) {
    const [data, setData] = useState('In the request... ');

    useEffect(() = > {
        const getData = async() = > {const result = await fetchData(id);
            setData(result);
        };
        getData();
        return () = > {
            // Cancel the network request
            removeFetch(id)
        }
    }, [id]);

    return <p>result: {data}</p>;
}
Copy the code

Show results:

No matter how the page is refreshed, only the result of the request with ID 10 is displayed.

  • Set the Boolean variable to trace
// Simulate a network request
const fetchData = id= >
    new Promise(resolve= > {
        setTimeout(() = > {
            const result = ` id for${id}Request result ';
            resolve(result);
        }, Math.random() * 1000 + 1000);
    });

function Counter({ id }) {
    const [data, setData] = useState('In the request... ');

    useEffect(() = > {
        let didCancel = false;
        const getData = async() = > {const result = await fetchData(id);
            if (!didCancel) {
                setData(result);
            }
        };
        getData();
        return () = > {
            didCancel = true;
        };
    }, [id]);

    return <p>result: {data}</p>;
}
Copy the code

You can see that no matter how the page is refreshed, only the result of the request with ID 10 is displayed.

5. How to save non in function componentsstate,propsThe value of the

Function components do not point to this, so in order to save component instance properties, useRef can be used for operations

The refs of function components have the ability to penetrate closures. By converting a value of a common type to an object reference with a current property, the property value is kept up to date each time it is accessed.

Is guaranteed to be accessed in every frame of the function componentstateThe values are the same

  • Let’s see if we don’t use ituseRefIn the case of each framestateHow are values printed
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() = > {
        const handleClick = function() {
            console.log('count: ', count);
        }
        window.addEventListener('click', handleClick, false)});return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code

Click the p label 5 times and then click the window object to see the print result:

  • useuseRefAnd then, in every framerefHow are values printed
function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(count);

    useEffect(() = > {
        // Set the latest state to countref.current
        countRef.current = count;
        const handleClick = function () {
            console.log('count: ', countRef.current);
        };
        window.addEventListener('click', handleClick, false);
    });

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

The same operation as before, click the P label 5 times, then click the Window interface, you can see the print result

Using useRef ensures that the state value accessed in each frame of the function component is the same.

How do I save properties of a function component instance

The function component has no instance, so properties cannot be mounted on this. What if we want to create a non-state, props variable that can be created and destroyed along with the function component?

Again, you can use useRef, which not only works on the DOM, but also converts ordinary variables into objects with the current attribute

For example, we want to set up an instance of Model that will generate the Model instance when the component is created, and will automatically generate a new Model instance when the component is destroyed and recreated

class Model {
    constructor() {
        console.log('create Model');
        this.data = []; }}function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(new Model());

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

In this way, when the function component is created, an instance of Model is generated and mounted to the current property of countRef. CountRef is not reassigned when it is rerendered.

This means that the same Model instance is used until the component is uninstalled, after which the current Model instance is destroyed.

A close look at the console output shows that while countRef is not reassigned, the Model constructor is still executed multiple times while the component is being rerendered

So at this point we can borrow the useState feature and rewrite it.

class Model {
    constructor() {
        console.log('create Model');
        this.data = []; }}function Counter() {
    const [count, setCount] = useState(0);
    const [model] = useState(() = > new Model());
    const countRef = useRef(model);

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

Using this way, you can use properties in the Model instance without changing state, flag, data source, or even Mobx store.

Six, useCallback

How do you avoid frequent useCallback execution when dependencies change frequently?

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

    const handleClick = useCallback(() = > {
        setCount(count + 1);
    }, [count]);

    return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code

Here, we extract the Click event and wrap it with a useCallback, but that doesn’t work very well.

Because the Counter component rerenders currently only rely on count changes, the useCallback is used and not used here.

useuseReduceralternativeuseState

You can use useReducer instead.

function Counter() {
    const [count, dispatch] = useReducer(x= > x + 1.0);

    const handleClick = useCallback(() = >{ dispatch(); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code

The dispatch function returned by useReducer comes with Memoize and does not change over multiple renders. Therefore dispatch is not required as a dependency in useCallback.

tosetStateIntermediate transfer function

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

    const handleClick = useCallback(() = > {
        setCount(c= > c + 1); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code

When you use a function as an argument in setCount, the value you receive is the most recent state value, so you can perform an operation with that value.

throughuseRefDo closure penetration

function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(count);

    useEffect(() = > {
        countRef.current = count;
    }, [count]);

    const handleClick = useCallback(() = > {
        setCount(countRef.current + 1); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code

The same effect can be achieved this way. Not recommended, not only does it require more code, but it can cause unexpected problems.

Seven, useMemo

Some of the problems and solutions with useCallback are described above. Let’s take a look at useMemo.

UseMemo and React.memo are different:

  • useMemoSome data inside the component is optimized and cached, lazy processing.
  • React.memoIt’s wrapped around the function component, inside the componentstatepropsPerform a shallow comparison to determine if rendering is required.

Difference between useMemo and useCallback

  • useMemoIs a value that can be a property or a function (including a component).
  • useCallbackThe return value of can only be functions

Therefore, useMemo can replace useCallback to some extent. Equivalent conditions: useCallback(fn, DEps) => useMemo(() => FN, DEps)

Therefore, some of the optimizations mentioned above about useCallback also apply to useMemo.

Should useCallback and useMemo be used frequently

Here is my humble opinion: it is not recommended to use frequently

Hey, guys, before you say anything, let me just say something

The reason:

  • UseCallback and useMemo are actually called as functions in the function component, so the first argument is the callback we pass. This callback will be created with or without useCallback and useMemo, so it will not reduce the cost of creating the function
  • Not only does it fail to reduce the cost of creation, but with useCallback and useMemo, the second parameter dependency also needs to perform a shallow comparison at each render, which virtually increases the cost of data comparison
  • Therefore, using useCallback and useMemo will not only reduce the workload, but also increase the cost of comparison, so it is not recommended to use it frequently

UseCallback and useMemo do not make sense, of course not. React is not useful at all.

We still need to use it, but we need to make a judgment based on the situation and when to use it.

The following describes some scenarios where useCallback and useMemo apply

Usage scenarios of useCallback

  1. Scenario 1: Sub-components need to be optimized for performance

    In this example, the App passes a function property onClick to the child component Foo

    Code before optimization using useCallback

    App.js

    import React, { useState } from 'react';
    import Foo from './Foo';
    
    function App() {
        const [count, setCount] = useState(0);
    
        const fooClick = () = > {
            console.log('Clicked the button on the Foo component');
        };
    
        return (
            <div style={{ padding: 50}} >
                <Foo onClick={fooClick} />
                <p>{count}</p>
                <button onClick={()= > setCount(count + 1)}>count increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    Foo.js

    import React from 'react';
    
    const Foo = ({ onClick }) = > {
    
        console.log('Foo component: render');
        return <button onClick={onClick}>Button in the Foo component</button>;
    
    };
    
    export default Foo;
    Copy the code

    If you click the Count Increment button in the App, you can see that the child component Foo is rerendered every time, but when the count changes, the parent component is rerendered, while the child component does not need to be rerendered.

    However, if the Foo component is a very complex and large component, then it is necessary to optimize the Foo component and useCallback can come in handy.

    Optimized code using useCallback

    App.js wraps function properties passed to child components in useCallback

    import React, { useCallback, useState } from 'react';
    import Foo from './Foo';
    
    function App() {
        const [count, setCount] = useState(0);
    
        const fooClick = useCallback(() = > {
            console.log('Clicked the button on the Foo component'); } []);return (
            <div style={{ padding: 50}} >
                <Foo onClick={fooClick} />
                <p>{count}</p>
                <button onClick={()= > setCount(count + 1)}>count increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    In foo.js, use react. memo to wrap components (same effect as inheriting PureComponent from class components).

    import React from 'react';
    
    const Foo = ({ onClick }) = > {
    
        console.log('Foo component: render');
        return <button onClick={onClick}>Button in the Foo component</button>;
    
    };
    
    export default React.memo(Foo);
    Copy the code

    Click the Count Increment button again to see that the parent component is updated, but the child component is not rerendered

  2. Scenario 2: Needed as a dependency for other hooks, demonstrated here using useEffect only

    When the page changes, we want to trigger useEffect to call the network request. In useEffect, getDetail is called. In order to use the latest page, So useEffect relies on the getDetail function to call the latest getDetail

    useuseCallbackCode before processing

    App.js

    import React, { useEffect, useState } from 'react';
    
    const request = (p) = >
        new Promise(resolve= > setTimeout(() = > resolve({ content: The first `${p}Page data ` }), 300));
    
    function App() {
        const [page, setPage] = useState(1);
        const [detail, setDetail] = useState(' ');
    
        const getDetail = () = > {
            request(page).then(res= > setDetail(res));
        };
    
        useEffect(() = > {
            getDetail();
        }, [getDetail]);
    
        console.log('App component: render');
    
        return (
            <div style={{ padding: 50}} >
                <p>Detail: {detail.content}</p>
                <p>Current page: {page}</p>
                <button onClick={()= > setPage(page + 1)}>page increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    However, following the above writing method will cause the App component to render in an infinite loop, in which case, useCallback is needed for processing

    useuseCallbackProcessed code

    App.js

    import React, { useEffect, useState, useCallback } from 'react';
    
    const request = (p) = >
        new Promise(resolve= > setTimeout(() = > resolve({ content: The first `${p}Page data ` }), 300));
    
    function App() {
        const [page, setPage] = useState(1);
        const [detail, setDetail] = useState(' ');
    
        const getDetail = useCallback(() = > {
            request(page).then(res= > setDetail(res));
        }, [page]);
    
        useEffect(() = > {
            getDetail();
        }, [getDetail]);
    
        console.log('App component: render');
    
        return (
            <div style={{ padding: 50}} >
                <p>Detail: {detail.content}</p>
                <p>Current page: {page}</p>
                <button onClick={()= > setPage(page + 1)}>page increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    Now you can see that the App component can render normally. This is demonstrated using only useEffect, which needs to be optimized as a dependency for other hooks

  3. Summary of useCallback Usage scenarios:

    1. When function properties are passed to a child component and the child component needs to be optimized, the function properties need to be wrapped useCallback

    2. Functions need to be useCallback wrapped when they are dependent on other hooks

Usage scenarios of useMemo

  1. This is similar to useCallback scenario 1, where performance tuning of subcomponents is required

  2. This is also similar to useCallback Scenario 2: when you need to rely on other hooks

  3. When large or complex operations are required, useMemo can be used for data caching to improve performance

    Again, useMemo’s data caching capabilities are used, and functions wrapped in useMemo are not re-executed until the dependency changes

    Consider the following example of two states in an App component: If you click increment, count will be incremented. If you click fresh, you will retrieve the dataSource again, but you do not need to display the dataSource. Instead, we need to display the sum of all the elements in our dataSource, so we need a new variable, sum, to display on the page.

    So let’s look at the code

    useuseMemoCode before optimization

    App.js

    import React, { useState } from 'react';
    
    const request = () = >
        new Promise(resolve= >
            setTimeout(
                () = > resolve(Array.from({ length: 100 }, () = > Math.floor(100 * Math.random()))),
                300));function App() {
        const [count, setCount] = useState(1);
        const [dataSource, setDataSource] = useState([]);
    
        const reduceDataSource = () = > {
            console.log('reduce');
            return dataSource.reduce((reducer, item) = > {
                return reducer + item;
            }, 0);
        };
    
        const sum = reduceDataSource();
    
        const refreshClick = () = > {
            request().then(res= > setDataSource(res));
        };
    
        return (
            <div style={{ padding: 50}} >
                <p>DataSource {sum}</p>
                <button onClick={refreshClick}>Refresh</button>
                <p>Current count: {count}</p>
                <button onClick={()= > setCount(count + 1)}>increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    Open the console, and you can see that the reduceDataSource function will be executed once, regardless of whether the INCREMENT or Refresh button is clicked. However, the dataSource has 100 elements. So we definitely want to recalculate the sum when the dataSource changes, which is where useMemo comes in.

    useuseMemoOptimized code

    App.js

    import React, { useMemo, useState } from 'react';
    
    const request = () = >
        new Promise(resolve= >
            setTimeout(
                () = > resolve(Array.from({ length: 100 }, () = > Math.floor(100 * Math.random()))),
                300));function App() {
        const [count, setCount] = useState(1);
        const [dataSource, setDataSource] = useState([]);
    
        const sum = useMemo(() = > {
            console.log('reduce');
            return dataSource.reduce((reducer, item) = > {
                return reducer + item;
            }, 0);
        }, [dataSource]);
    
        const refreshClick = () = > {
            request().then(res= > setDataSource(res));
        };
    
        return (
            <div style={{ padding: 50}} >
                <p>DataSource {sum}</p>
                <button onClick={refreshClick}>Refresh</button>
                <p>Current count: {count}</p>
                <button onClick={()= > setCount(count + 1)}>increment</button>
            </div>
        );
    }
    
    export default App;
    Copy the code

    You can see that the functions in the useMemo will only be re-executed when the Refresh button is clicked. When the increment button is clicked, the sum is the same as the cached sum and will not be recalculated.

  4. Summary of useMemo Usage scenarios:

    1. When a reference type property is passed to a child component and the child component needs to be optimized, useMemo wraps the property

    2. Reference type values, which need to be wrapped in useMemo when using as dependencies for other hooks that return property values

    3. To improve performance, you can use the useMemo to cache data and save computing costs

Therefore, in the use of useCallback and useMemo, it is not necessary to use them if it is not necessary. Frequent use may increase the cost of dependency comparison and reduce performance.

How do I call a child’s state or method from a parent component

In a function component, there is no component instance, so you cannot invoke a state or method in a child component by binding its instance, as in a class component.

So in a function component, how does a parent component call a child component’s state or method? The answer is to use the useImperativeHandle

grammar

useImperativeHandle(ref, createHandle, [deps])

  • The first parameter is the ref value, which can be passed in as an attribute or used with the forwardRef

  • The second argument is a function that returns an object whose properties are mounted to the current property of the first argument, ref

  • The third parameter is the set of dependent elements, like useEffect, useCallback, and useMemo. When the dependency changes, the second parameter is reexecuted and remounted to the current property of the first parameter

usage

Note:

  • The third parameter, dependency, must be filled in as required, too little will result in object property exceptions returned, too much will result increateHandlerepeat
  • A component orhookFor the sameref, can only be used onceuseImperativeHandle, several times, the following implementationuseImperativeHandlecreateHandleThe return value replaces the previous oneuseImperativeHandlecreateHandleThe return value

Foo.js

import React, { useState, useImperativeHandle, useCallback } from 'react';

const Foo = ({ actionRef }) = > {
    const [value, setValue] = useState(' ');

    /** * randomly changes the value of the function */
    const randomValue = useCallback(() = > {
        setValue(Math.round(Math.random() * 100) + ' '); } []);/** * submit function */
    const submit = useCallback(() = > {
        if (value) {
            alert('Submitted successfully, user name:${value}`);
        } else {
            alert('Please enter a user name! ');
        }
    }, [value]);

    useImperativeHandle(
        actionRef,
        () = > {
            return {
                randomValue,
                submit,
            };
        },
        [randomValue, submit]
    );

    / *!!!!! To return multiple properties like this, UseImperativeHandle useImperativeHandle(actionRef, () => {return {submit,}} [submit]) useImperativeHandle(actionRef, () => { return { randomValue } }, [randomValue]) */

    return (
        <div className="box">
            <h2>Function component</h2>
            <section>
                <label>User name:</label>
                <input
                    value={value}
                    placeholder="Please enter user name"
                    onChange={e= > setValue(e.target.value)}
                />
            </section>
            <br />
        </div>
    );
};

export default Foo;

Copy the code

App.js

import React, { useRef } from 'react';
import Foo from './Foo'

const App = () = > {
    const childRef = useRef();

    return (
        <div>
            <Foo actionRef={childRef} />
            <button onClick={()= >Childref.current.submit ()}> calls the submission function of the child component</button>
            <br />
            <br />
            <button onClick={()= >ChildRef. Current. RandomValue ()} > random modify child components of the input value</button>
        </div>
    );
};

Copy the code

X. Reference documents

  • UseEffect complete guide
  • React Hooks first issue: useCallback

Write in the back

If there is something wrong or not precise, you are welcome to put forward valuable suggestions, thank you very much.

Welcome Star if you like it or if you are helpful, it is also a kind of encouragement and support for the author.