useCallback
takes a callback function and an array of dependencies as arguments, and returns the cached callback function if each item in the dependency array remains the same.
Since useCallback
can cache functions, wouldn’t all functions in the Function component be wrapped in useCallback
for the purpose of React
application optimization? Don’t worry, let’s take a look at two examples and decide which one is better, App1
or App2
?
The difference between App1
and App2
is that onValueChange
uses useCallback
in the former and not in the latter. We know from the definition that useCallback
can cache functions, and that App1
will not generate a new onValueChange
when value
changes, so the first impression is that App1
is definitely better.
Now, rewrite App1
and determine which is better, App1
or App2
?
|
|
The above App1
is actually the same as the original App1
, but written in a different way, compared to App2
, which has one more array definition and one more useCallback
call than App2
, so contrary to the initial intuition, App2
is better than App1
which uses useCallback
. performs better than
App1with
useCallback`.
So you shouldn’t use useCallback
, because useCallback
will lead to worse performance ?
Of course not, useCallback
can improve React’s performance, but only if it’s a prerequisite. Modify the above example to add a clear function to the input box:
In the above example, ClearButton
accepts props from onClear
and clears the input when the button is clicked. The problem with this example, however, is that when the value
changes, the ClearButton
is also re-rendered as seen in the Console
. From a performance point of view, a change in value
should not cause ClearButton
to re-render, because ClearButton
only relies on onClear
, but a change in value
causes onClear
to re-generate, resulting in prevProps.onClear ! == nextProps.onClear
, which causes ClearButton
to be re-rendered. This is where useCallback
comes in handy.
In the example above, onClear
is wrapped in useCallback
, so that a change in value
does not cause onClear
to be re-generated. But from the Console
, even with useCallback
, a change in value
still causes ClearButton
to be re-rendered.
This brings us back to the rendering of React
, where its own rendering must cause a re-rendering of the child component for the React
component, which can be verified by removing onClear
from the ClearButton
.
|
|
In the above example, even though ClearButton
does not have any props
, the value
change still causes ClearButton
to be re-rendered. Why does this happen? Rewrite the App
above and it becomes clear.
|
|
The second argument to React.createElement
is the component’s props
, and you can see that even though ClearButton
doesn’t have any props
, each render of App
still passes a new empty object as its props
, which is why ClearButton
re-renders This is why ClearButton
re-renders.
So how can we avoid this unnecessary rendering? The answer is the shouldComponentUpdate
lifecycle for Class components and React.memo
for Function components, shouldComponentUpdate
and React.memo
allow the developer to manually determine if the component needs to be re-rendered.
For the above example, simply wrapping React.memo
around ClearButton
would prevent ClearButton
from being re-rendered.
So, useCallback
is not a silver bullet for React
performance optimisation, rather, the wrong use of it can lead to negative optimisation.
useMemo
In fact, useCallback
is syntactic sugar for useMemo
, and useCallback(fn, dependencies)
is equivalent to useMemo(() => fn, dependencies)
. As with useCallback
, useMemo
is not used as much as it should be, so when should you use useMemo
?
In the first case, as with useCallback
, when a child component uses shouldComponentUpdate
or React.memo
, the value calculated by useMemo
ensures that the props
remain the same, thus avoiding repeated rendering by the child component.
The second case is where the performance consumption of the value calculation exceeds the performance consumption of the call to useMemo
. Similar to the decomposition of useCallback
above, using useMemo
adds a computed function fn
generation, a dependency array dependencies
generation and a single call to useMemo
, if the value calculation requires more performance than the call to useMemo
, then using useMemo
is can be optimized.