20

What do they mean by memoized callbacks and what does useCallback actually do?

 4 years ago
source link: https://www.tuicool.com/articles/E7bey2J
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

When I first read that useCallback returns a "memoized callback" I thought I knew what it meant.

I used both callback functions and memoized functions before so I was like "Ah, okay, it's just memoization.".

Well, it turns out that seeing the word "memoization" and immediately assuming that it means what it usually does was a mistake.

Much to my surprise, useCallback does NOT return a memoized function.

According to Wikipedia, memoization is about limiting the number of expensive function calls by using some sort of a cache. eUbU32y.png!web

For example, this is how we can use _.memoize from lodash to memoize a function.

First, we obtain a memoized version of a potentially expensive function (here it's just the double function).

ABF32qJ.png!web

The first time we call memoizedDouble(42) , it calls the original double function under the hood and returns its result, which is also stored in the cache.

JfqaM3Y.png!web

The next time we call memoizedDouble(42) , the result is obtained from the cache. The original double function is not called at all.

QnaYvuB.png!web

We can keep calling memoizedDouble(42) , but double will never be called again. Unless the cache is cleared, the results will be fetched from there.

jyQ3ye2.png!web

If there's no cached result for some input, the original function will be called once, and then the result will be added to the map. 7jI7nyn.png!web

To sum it up, two functions were created the original double function and the memoizedDouble function. Calling memoizedDouble 4 times with 2 different inputs resulted in only 2 calls of the original double function.

This was memoization. The useCallback hook does something else. Let's see what's that.

Here we have a callback cb that calls the double function. We use useCallback to get a memoized version of the cb callback. The first time the component renders, a new cb is created and the very same cb function is returned by useCallback .

3YvEbia.png!web

When memoizedCb is called, it calls the original cb callback, which eventually calls the double function.

IVR3YrR.png!web

The next time the component renders, another cb is created . However, because the dependencies passed to useCallback haven't changed, it's thrown away and memoizedCb stays the same cb it was during the first render.

my2mInE.png!web

The third time the component is rendered with the same dependencies, yet another cb is created and then thrown away. Calling the memoizedCb results in calling the same, old cb , which then calls the original double function again. uyiYJrQ.png!web

Unless the dependencies change, every render will create a new unused callback, and every call to the memoized callback, which is the old one, will actually call the original double function. zUziE3M.png!web

When the dependencies finally change, the memoizedCb also changes. ZvieQzj.png!web

And similarly to how the previous memoizedCb was calling the first cb , this one also calls the first cb it got since the dependencies changed. jiuUZrr.png!web

To sum it up, 6 new callbacks were created during the 6 renders. Calling the memoized callback 4 times caused 4 calls of the original callback.

So, if useCallback does neither limit the number of function calls nor functions being created, what is the value it provides?

What helped me to finally wrap my head around it it was to ignore the meaning of memoization. Let's pretend this word doesn't even occur in the documentation.

So far we've focused almost solely on the "memoized" adjective, but we haven't talked about "callbacks" yet. So, what's the purpose of a callback? It's a function that we pass down to some other function and we expect it to be called once something happens, like a query finished or an error occurs. It is a way to establish communication between various pieces of code. The module that calls the callback is not consuming the result, but the code passing the callback down expects to receive a call when an event occurs.

As we can see, side effects are the most important thing when it comes to callbacks. The results usually don't matter that much. What's crucial is that when one piece of code calls the callback, the other one receives the call. We don't want to lose any calls. That's why memoizing a callback would actually cut the communication link between modules in our application!

What useCallback does is limit the number of identical callbacks floating around. As we can see in the picture visible above, across the first 4 renders, when the dependency was the number 42, memoizedCb stayed exactly the same cb function it was during the first render. It wasn't wrapped in anything. The results weren't returned from any cache. All what useCallback ensured was that memoizedCb equaled the first cb since the last time the dependencies changed.

Does it improve the performance? Not on its own. It may actually decrease it a bit, if no other code relies on referential equality of such callbacks.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK