We know that useMemo
and useCallback
are mainly used to cache intermediate states and reduce meaningless render
to improve performance. But recently I have found that I have been misunderstanding their use!
misunderstanding of useMemo
Please take a look at the following code. Even with useMemo
, the second subcomponent is re-rendered without the change of isZero
!
import { useCallback, useMemo, useState } from "react";
const Child = ({ value, onClick }) => {
return (
<div
style={{
height: 100,
background: `#${(~~(Math.random() * (1 << 24))).toString(16)}`
}}
>
my value is {value.toString()}
</div>
);
};
export default function App() {
const [count, setCount] = useState(0);
const isZero = useMemo(() => !!(count % 3), [count]);
const onClick = useCallback(() => setCount(count + 1), [count]);
return (
<div className="App">
<button onClick={onClick}>click me</button>
<Child value={count} />
<Child value={isZero} />
</div>
);
}
💡 related reading
in fact, the reason has also been mentioned in previous articles:
React
every time the state of a component changes, it starts from the current component until all leaf node components are re-rendered.
The
article also mentions the solution to this problem: the subcomponent is wrapped with the memo
function, and the component can render as expected.
however, at this point we remove useMemo
, and the subcomponents are still rendered as expected.
memo
is similar touseMemo
, which is based on a shallow comparison ofObject.is
and is only valid for non-reference types.
so in the above example, it makes no sense to use useMemo
.
misunderstanding of useCallback
however, in the above example, the component renders as expected even if the onClick
function does not use useCallback
. This is because the App
component is destined to be rendered regardless of whether the cache of the callback function of onClick
has changed.
so, now we have a reasonable code, as follows:
import { memo, useCallback, useMemo, useState } from "react";
const Child = memo(({ value, onClick }) => {
return (
<div
style={{
height: 100,
background: `#${(~~(Math.random() * (1 << 24))).toString(16)}`
}}
>
my value is {value.toString()}
</div>
);
});
export default function App() {
const [count, setCount] = useState(0);
// const isZero = useMemo(() => !!(count % 3), [count]);
const isZero = !!(count % 3);
// const onClick = useCallback(() => setCount(count + 1), [count]);
const onClick = () => setCount(count + 1);
return (
<div className="App">
<button onClick={onClick}>click me</button>
<Child value={count} />
<Child value={isZero} />
</div>
);
}
when on earth should I use useCallback
?
take a look at the example below. Add the following code to the above code:
const onClickMethod = () => console.log("lll");
return (
<div className="App">
<button onClick={onClick}>click me</button>
<Child value={count} onClick={onClickMethod} />
<Child value={isZero} onClick={onClickMethod} />
</div>
);
at this point, it is found that the component cannot render as expected, and the second Child
component will be re-rendered regardless of whether the Child
has changed.
this is because the onClickMethod
method at this time is taken as the onClick
property of the Child
component.
if you now wrap the onClickMethod
method with useCallback
, it will be normal again.
const onClickMethod = useCallback(() => console.log("lll"), []);
this is the correct use of useCallback
!
Summary
when we write components, we should follow the following rules, which can effectively improve page performance:
-
wrap components (reduce rendering times) with
memo
methods as much as possible -
use
useMemo
(reduce rendering times)when the attribute of a child component is in an intermediate state of a non-reference type.
-
use
useCallback
(reduce rendering times)when the property of the subcomponent is a function
-
only works on attributes within the scope of the current component. Try not to use
useMemo
anduseCallback
(reduce calls)
well, that's all for today's sharing. I hope you don't misuse useMemo
and useCallback
as I do!