preface
As React users, when trying to optimize existing code, we may try to use useMemo and useCallback to cache data or functions. In the next component update, if the corresponding dependencies do not change, the cache value can be obtained without recalculation.
Next, I will analyze useMemo and useCallback in terms of usage and principle
Use useMemo and useCallback
import React, { useState, useMemo, useCallback } from 'react'
function Demo () {
const [count, setCount] = useState(0)
const [isChanged, setIsChanged] = useState(false)
// // useCallback takes two parameters, a function that needs to be executed when a dependency changes and a dependency
const calDoubleCount = useCallback(() = > {
setCount(count= > count + 1)}, [])// useMemo takes two arguments, one is the function that needs to be executed when the dependency changes, and the other is the dependency
const doubleCount = useMemo(() = > {
return count * 2
}, [count])
return (
<>
<p onClick={()= >setIsChanged(isChanged => ! isChanged)}>change me</p>
<p onClick={()= > setCount(count => count + 1)}>{doubleCount}</p>
<button onClick={calDoubleCount}>click me</button>
</>)}Copy the code
The above code describes how useMemo is used in real development. We change the count value by clicking on the second P tag, and the doubleCount dependency has count, so when count changes, DoubleCount recalculates and returns the latest value.
When the first P tag is clicked, because there is no change in count, doubleCount uses the last calculated value instead of recalculating
UseMemo source code analysis
Before talking about useMemo source code, there is a premise that we need to understand clearly, is that each hook execution we can be divided into two parts
- mount
- update
The first time a component is mounted, it goes mount, and every subsequent component rendering goes Update. Let’s look at a hook related source code.
// mount
const HooksDispatcherOnMount: Dispatcher = {
useCallback: mountCallback,
useMemo: mountMemo,
// omit other hooks
};
// update
const HooksDispatcherOnUpdate: Dispatcher = {
useCallback: updateCallback,
useMemo: updateMemo,
// omit other hooks
};
Copy the code
HooksDispatcherOnMount; HooksDispatcherOnUpdate; HooksDispatcherOnUpdate; With this premise in mind, let’s look at the following code.
// mount
HooksDispatcherOnMountInDEV = {
useMemo<T>(create: () = > T, deps: Array<mixed> | void | null): T {
try {
return mountMemo(create, deps);
} finally {
/ / to omit}}}// update
HooksDispatcherOnUpdateInDEV = {
useMemo<T>(create: () = > T, deps: Array<mixed> | void | null): T {
try {
return updateMemo(create, deps);
} finally {
/ / to omit}}}Copy the code
That is, the useMemo called when the component is first mounted is actually called mountMemo. Every time I do that, I call updateMemo.
mountMemo
function mountMemo<T> (
nextCreate: () => T, // The function that needs to be executed when a dependency changes
deps: Array<mixed> | void | null./ / rely on
) :T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Copy the code
NextCreate is actually the first argument we pass to useMemo, which is a callback function that gets executed when a dependency is updated, and dep is the second argument we pass, which is an array of dependencies.
As you can see, for the first time a component is mounted, useMemo executes nextCreat directly, returning the calculated value. While Hook. memoizedState stores the calculated values and corresponding dependencies, the next time useMemo is executed, the purpose is to determine whether the dependency has changed to return the recalculated value or the result of the last calculation.
updateMemo
function updateMemo<T> (
nextCreate: () => T,
deps: Array<mixed> | void | null.) :T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// memoizedState is [value, deps]
const prevState = hook.memoizedState;
if(prevState ! = =null) {
// If the dependency is not null
if(nextDeps ! = =null) {
// Dependencies since last update
const prevDeps: Array<mixed> | null = prevState[1];
// Compare the current and last dependencies
if (areHookInputsEqual(nextDeps, prevDeps)) {
// If the two dependencies are equal, return the result of the previous calculation directly
return prevState[0]; }}}// Otherwise recalculate the dependency value
const nextValue = nextCreate();
// Assign the recalculated values and dependencies to hook.memoizedState
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Copy the code
useCallback
Now that we know how useMemo works, useCallback is pretty simple.
UseCallback is almost identical to useMemo, except that useMemo returns the value calculated by the function, whereas useCallback returns the function itself
mountCallback
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 the function itself directly
return callback;
}
Copy the code
updateCallback
function updateCallback<T> (callback: T, deps: Array<mixed> | void | null) :T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState; // [callback, deps]
if(prevState ! = =null) {
// Whether there are dependencies
if(nextDeps ! = =null) {
const prevDeps: Array<mixed> | null = prevState[1];
// Compare the current dependency to the dependency after the last calculation
if (areHookInputsEqual(nextDeps, prevDeps)) {
If so, this returns the last cached value
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
// Return the function itself directly
return callback;
}
Copy the code
The only difference between updateCallback and updateMemo is that updateMemo internally executes a callback and returns the value. UseCallback returns the function directly without evaluating it.
conclusion
From the analysis of useMemo and useCallback above, we can see that the difference between useMemo and useCallback is simple. I’ve written all the rewrites up here, but I want to make one last point here. Although both useMemo and useCallback can cache data, it should not be abused. We should consider which data is worth caching, because for useMemo and useCallback, in addition to calculating values, it takes time to compare dependency changes. We should measure this so that we can use hooks better, rather than optimizing for optimization’s sake.