How does useMemo and useCallback work ?
When render a component, React will process the whole Component code. There might be several computed values and functions that its values only depends on few dependencies and does not need to re-calculate everytime.
If these values require a lot of time to calculate, it will let the render process down as consequent.
In order to improve performance, React produce useMemo and useCallback.
Implement it quite simple:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
The way it work are similar to each other and quite easy to understand.
💥 All the code reference I mentioned in this article was translated to javascript
and cut-off **comments* and warning parts just for shorter code and more clear.
Click View Source to see the original code*
1.Mount Phase (First Render)
function mountMemo(nextCreate, deps){
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function mountCallback(callback, deps){
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
When component was rendered on first time, useMemo
will execute the create function (nextCreate) to get return value and then store the value and dependency list into memoizedState
, meanwhile useCallback
store all of its inputs.
2.Update Phase
function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
When re-render a component, useMemo
and useCallback
will compare old dependency (that was already memoized) with the new one.
If one of those are null
or they are difference (result from areHookInputsEqual
), useMemo
and useCallback
will store and return the new value.
Otherwise, return memoized value.
Compare hook dependency function
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
React will use Object.is
to compare previos and next value of dependency. You may find the difference of its behavior here.
function is(x, y) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
const objectIs = typeof Object.is === 'function' ? Object.is : is;
export default objectIs;
FYI
🎉 As React announcement in July 2022, there might be a new Compiler that auto generate useMemo
and useCallback
for us.
You can read this post here: https://reactjs.org/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.html
Top comments (2)
Thank for sharing
Best post I've ever seen