useMemo

When we write a functional component, we often encounter that some methods are re-executed due to the change of non-dependent variables, resulting in a waste of performance. In this case, we can consider using useMemo to cache our value. Only when the value of the dependency changes, we take the re-execution method to obtain the new value. Otherwise, the last value is fetched from the cache and returned.

Scenarios and Examples

Let’s look at the following pseudocode:

function Demo(){
    const [count, setCount] = useState<number>(0);
    const [name, setName] = useState<string>("");
    
    const showCount = () = > {
        console.log("ShowCount executed");
        let sum = 0;
        for(let i=0; i<count; i++){ sum+=i; }return sum;
    };
    
    return (
    	<div>
        	<h2>This is a useMemo test instance (not optimized)</h2>
            <p>{showCount()}</p>
            <p>Counter: {count}</p>
            <button onClick={()= >SetCount (count + 1)}> increment</button>
            <input value={name} onChange={e= > setName(e.target.value)} />
        </div>
    );
}
Copy the code

The pseudo code above is not useMemo optimized code, when we click the add button, it does achieve the desired effect, both the cumulative result and the counter change. However, when we set the value of the textbox, we only changed the name, not the count. We expected that we would not restart the showCount method to perform the recalculation, but the above code would still fire repeatedly. We can imagine that if the showCount method performs an extremely complex and time-consuming performance calculation, this seemingly few lines of code could cause the entire site to stall or even crash.

So, let’s use useMemo to modify the above code:

function Demo(){
    const [count, setCount] = useState<number>(0);
    const [name, setName] = useState<string>("");
    
    // Use useMemo to process the calculation method, only when the dependent variable count changes will trigger recalculation to obtain a new result, otherwise will directly get the result of the previous calculation cache directly returned, to avoid undisputed double calculation
    const showCount = useMemo(() = > {
        console.log("ShowCount executed");
        let sum = 0;
        for(let i=0; i<count; i++){ sum+=i; }return sum;
    }, [count]);
    
    return (
    	<div>
            <h2>This is a useMemo test instance (not optimized)</h2>
            <p>ShowCount {showCount}</p>
            <p>Counter: {count}</p>
            <button onClick={()= >SetCount (count + 1)}> increment</button>
            <input value={name} onChange={e= > setName(e.target.value)} />
        </div>
    );
}
Copy the code

The use of,

useMemo(nextCreateFn, deps)

Official explanation:

Returns an memoized value.

You pass in the create function and the dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render.

Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo.

If the dependency array is not provided, useMemo evaluates the new value each time it renders.

** You can use useMemo as a means of performance optimization, but don’t use it as a semantic guarantee. ** In the future React may choose to “forget” some of the previous Memoized values and recalculate them at the next render, such as freeing memory for off-screen components. Write code that can run without useMemo first — then add useMemo to your code to optimize performance.

NextCreateFn requires us to pass in a function that computes the desired result. This function requires a return value, and the return value of the function is our final result

Deps is a dependency array, and we need to add the external state used in the function, the dependency variables, so that if the dependency does not change, we can get the last cached result and return it directly, without having to repeat the nextCreateFn calculation. When deps is null, every render will be recalculated, thus losing the meaning of this hooks. Therefore, the program will not fail without passing this dependency argument. We also need to specify the function dependencies and pass in the dependency array during development, otherwise we do not need to use this hooks.

Source code analysis

React source useMemo implementation

// This function is used to compare whether two dependency arrays have the same dependency
function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null.) {
  // Remove some development environment debug code that is not logical

  // The last dependency was null, but the current dependency is not null. The dependency has changed, so return false
  if (prevDeps === null) {
   // Remove some development environment debug code that is not logical
    return false;
  }

  // Remove some development environment debug code that is not logical
  // Loop through each dependency and compare whether the previous dependency is the same as the current dependency. If one dependency is different, return false
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  // All dependencies are the same, and return true
  return true;
}
React useMemo useMemo useMemo useMemo useMemo useMemo
// React executes useMemo in two phases: mount and update
// When mounting, we will first execute the computation method we passed in, namely: nextCreate, to get the first calculation result nextValue, and then save the calculation result and dependency array in memoizedState and cache them, so that we can compare and get the cached result when updating. Finally, the first calculation is returned as the result of the first rendering
function mountMemo<T> (
  nextCreate: () => T,
  deps: Array<mixed> | void | null.) :T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
// When the component rerenders due to some operation, the last dependency array will be taken out and compared with the current dependency array. If the dependency state has not changed, the last calculation result cached in memoizedState will be returned directly without having to re-execute nextCreate for recalculation. Otherwise, recalculate, and cache the latest calculation with the new dependent array, and return the new calculation as the result of this rendering
function updateMemo<T> (
  nextCreate: () => T,
  deps: Array<mixed> | void | null.) :T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if(prevState ! = =null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if(nextDeps ! = =null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0]; }}}const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
Copy the code

useCallback

Scenarios and Examples

We often bind events to elements, or pass functions to child components as properties. In a functional component, if an unprocessed function is passed to child components as properties, then when any state of the parent component changes and is rerendered, It can also cause child components to be re-rendered due to property changes because each function is a new reference. Let’s take a look at the following example to make it clear:

function Demo(){
    const [count, setCount] = useState<number>(0);
    const [name, setName] = useState<string>("");
    
    const showCount = () = > {
        console.log("ShowCount executed");
        let sum = 0;
        for(let i=0; i<count; i++){ sum+=i; }return sum;
    };
    
    return (
    	<div>
        	<h2>This is a useMemo test instance (not optimized)</h2>
            <p>Counter: {count}</p>
            <button onClick={()= >SetCount (count + 1)}> increment</button>
            <input value={name} onChange={e= > setName(e.target.value)} />
            <Child onClick={showCount} />
        </div>
    );
}
function Child(props) {
    console.log("child rerender!!");
    return <div onClick={props.onClick}>This is the child node</div>
}
Copy the code

In the example above, we pass the showCount function as a property to the Child. When any state of our parent Demo, such as count and name, changes, the showCount function is recreated, resulting in inconsistent function references and triggering a re-rendering of the Child component. However, our showCount method is obviously independent of our name state, so we expect the Child component to be rerendered only when the count state changes. So, at this point we can use the useCallback.

function Demo(){
    const [count, setCount] = useState<number>(0);
    const [name, setName] = useState<string>("");
    
    // Use useCallback to optimize the function, we change our callback if and only if count changes, otherwise get the cached function directly, keeping references consistent
    const showCount = useCallback(() = > {
        console.log("ShowCount executed");
        let sum = 0;
        for(let i=0; i<count; i++){ sum+=i; }return sum;
    }, [count]);
    
    return (
    	<div>
        	<h2>This is a useMemo test instance (not optimized)</h2>
            <p>Counter: {count}</p>
            <button onClick={()= >SetCount (count + 1)}> increment</button>
            <input value={name} onChange={e= > setName(e.target.value)} />
            <Child onClick={showCount} />
        </div>
    );
}
function Child(props) {
    console.log("child rerender!!");
    return <div onClick={props.onClick}>This is the child node</div>
}
Copy the code

The use of,

useCallback(callback, deps)

Official explanation:

Returns a Memoized callback function.

Passing the inline callback function and the array of dependencies as arguments to useCallback returns the Memoized version of the callback function, which is updated only when a dependency changes. This is useful when you pass callback data to child components that are optimized and use reference equality to avoid unnecessary rendering (such as shouldComponentUpdate).

UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).

UseCallback and useMemo users are basically the same. They both pass in two parameters, the first is a function and the second is a dependent array. The difference is that useMemo executes the function we passed in to calculate the target result, whereas useCallback simply caches and returns the function we passed in. It’s not going to be implemented.

Source code analysis

React source code useCallback implementation

// Unlike useMemo, useCallback does not execute callback to get the result. Instead, useCallback caches and returns callback
function mountCallback<T> (callback: T, deps: Array<mixed> | void | null) :T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
// Update is the same as useMemo, except that no callback is performed to retrieve results
function updateCallback<T> (callback: T, deps: Array<mixed> | void | null) :T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if(prevState ! = =null) {
    if(nextDeps ! = =null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
Copy the code

conclusion

This time we discuss things actually very simple, it is estimated that we need not a few minutes to digest, is this point was singled out as a topic of discussion, because as our project for functional components and the use of hooks is more and more frequent, our business functions also break up more and more thin, components are getting smaller, We need from each little point in the well performance optimization, or on the page of introducing some without optimization component, may be infinite amplification, the problem of the performance of the small card or even collapse, eventually led to the page in the project use useMemo and useCallback, can make us as much as possible to avoid the occurrence of these situations, Make the project run more smoothly and efficiently.