Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.7k views
in Technique[技术] by (71.8m points)

reactjs - React useCallback with Parameter

Using React's useCallback hook is essentially just a wrapper around useMemo specialized for functions to avoid constantly creating new function instances within components' props. My question comes from when you need to pass an argued to the callback created from the memoization.

For instance, a callback created like so...

const Button: React.FunctionComponent = props => {
    const onClick = React.useCallback(() => alert('Clicked!'), [])
    return <button onClick={onClick}>{props.children}</button>
}

is a simple example of a memoized callback and required no external values passed into it in order to accomplish its job. However, if I want to create a generic memoized callback for a React.Dipatch<React.SetStateAction> function type, then it would require arguments...for example:

const Button: React.FunctionComponent = props => {
    const [loading, setLoading] = React.useState(false)
    const genericSetLoadingCb = React.useCallback((x: boolean) => () => setLoading(x), [])

    return <button onClick={genericSetLoadingCb(!loading)}>{props.children}</button>
}

In my head, this seems like its the exact same as doing the following...

const Button: React.FunctionComponent = props => {
    const [loading, setLoading] = React.useState(false)
    return <button onClick={() => setLoading(!loading)}>{props.children}</button>
}

which would let defeat the purpose of memoizing the function because it would still be creating a new function on every render since genericSetLoadingCb(false) would just be returning a new function on each render as well.

Is this understanding correct, or does the pattern described with arguments still maintain the benefits of memoization?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

For this example I will use a genericCb function instead of genericSetLoadingCb as you have:

const genericCb = React.useCallback((param) => () => someFunction(param), [])

Now, what we did above is ensure that function genericCb remains the same across renders (due to usage of useCallback).

However, each time you call genericCb to create a new function out of it like this:

genericCb("someParam") 

The returned function will still be different on each render. To also ensure the returned function is memoized for some input, you should additionally use some memoizing approach:

 let memoizedCb = React.useCallback(
    memoize((param) => () => someFunction(param)),
    []
  );

For example you can use fast-memoize library, e.g.

import memoize from "fast-memoize";

Now if you call memoizedCb("someParam") to generate a function, it will return same function on each render, provided "someParam" also remains the same.


Note: As pointed out in the comments this approach seems to produce warning (it didn't in my project though):

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

It seems the warning is there because since we passed memoize to useCallback, it can't see the dependencies anymore and can't warn you about possible stale closures. In that case IMHO it is up to the user to check if there are any stale closures.


Simply using memoize without useCallback wouldn't work, as on next render it would invoke memoize from fresh like this:

let memoized = memoize(fn)
 
memoized('foo', 3, 'bar')
memoized('foo', 3, 'bar') // cache hit

memoized = memoize(fn); // without useCallback this would happen on next render 

// Now the previous cache is lost

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...