React useCallback with Parameter

孤街浪徒 提交于 2021-01-21 06:35:11

问题


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?


回答1:


For the sake of example below I will use a genericCb function instead of genericSetLoadingCb as you have.

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

What we did above is ensure that function genericCb remains the same across rerenders. However, each time you create a new function out of it like this:

genericCb("someParam")

The returned function will be different on each render. To also ensure the returned function is memoized, you need something like this:

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

I use fast memoize, e.g.

import memoize from "fast-memoize";

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


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

Note: As pointed out by @Jeaf in Typescript this approach seems to produce warning

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

It seems the warning is there because useCallback can't see the dependencies hence can't warn you about possible stale closures. In that case it seems it is up to the user to decide whether that's an issue in his/her case.




回答2:


It seems like doing the following is an elegant and simple way to solve your problem. It won't create a new cb function if Button is just rerendering.

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


来源:https://stackoverflow.com/questions/61255053/react-usecallback-with-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!