使用React的useCallback
钩子本质上只是useMemo
的包装,专门用于函数,以避免在组件的道具中不断创建新的函数实例。我的问题来自于何时需要将一个辩论传递给由记忆创建的回调。
例如,像这样创建的回调…
const Button: React.FunctionComponent = props => {
const onClick = React.useCallback(() => alert('Clicked!'), [])
return <button onClick={onClick}>{props.children}</button>
}
是一个内存化回调的简单示例,不需要将外部值传递给它即可完成其工作。然而,如果我想为React.Dipatch<React.SetStateAction>
函数类型创建一个通用的内存化回调,那么它将需要参数。。。例如:
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>
}
在我看来,这似乎与做以下事情完全一样。。。
const Button: React.FunctionComponent = props => {
const [loading, setLoading] = React.useState(false)
return <button onClick={() => setLoading(!loading)}>{props.children}</button>
}
这将破坏记忆函数的目的,因为它仍将在每个渲染上创建新函数,因为CCD_。
这种理解是正确的,还是用论据描述的模式仍然保持着记忆的好处?
我将提供一个与用例略有不同的答案,但它仍然会回答您的问题。
动机和问题陈述
让我们考虑以下(类似于您的genericSetLoadingCb
(高阶函数genericCb
:
const genericCb = React.useCallback(
(param) => (e) => setState({ ...state, [param]: e.target.value }),
[]
);
假设我们在以下情况下使用它,其中Input
是使用React.memo创建的存储组件:
<Input value={state.firstName} onChange={genericCb('firstName')} />
由于Input
是内存化组件,我们希望genericCb('firstName')
生成的函数在重新渲染时保持不变,这样内存化组件就不会不必要地重新渲染。
下面我们将看到如何实现这一目标。
解决方案
现在,我们在上面构建genericCb
的方式是确保它在渲染中保持不变(由于使用了useCallback
(。
然而,每次调用genericCb
以创建一个新函数时,如下所示:
genericCb("firstName")
返回的函数在每个渲染上仍然不同。为了确保返回的函数对某些输入进行记忆,您还应该使用一些记忆方法:
import memoize from "fast-memoize";
....
const genericCb = React.useCallback(
memoize((param) => (e) => setState({ ...state, [param]: e.target.value })),
[]
);
现在,如果您调用genericCb("firstName")
来生成一个函数,它将在每个渲染上返回相同的函数,前提是"firstName"
也保持相同。
备注
正如上面评论中所指出的,使用useCallback
的解决方案似乎会产生警告(但在我的项目中没有(:
React Hook useCallback接收到一个函数,该函数的依赖项为未知的传递内联函数而不是
似乎存在警告,因为我们没有将内联函数传递给useCallback
。基于这个github线程,我发现消除这个警告的解决方案是使用useMemo
来模仿useCallback
,如下所示:
// Use this; this doesn't produce the warning anymore
const genericCb = React.useMemo(
() =>
memoize(
(param) => (e) => setState({ ...state, [param]: e.target.value })
),
[]
);
此外,我想注意的是,简单地使用没有useCallback
(或更新中的useMemo
(的memoize是不起作用的,因为在下一次渲染中,它将从fresh调用memoize,如下所示:
let memoized = memoize(fn)
memoized('foo', 3, 'bar')
memoized('foo', 3, 'bar') // cache hit
memoized = memoize(fn); // without useCallback (or useMemo) this would happen on next render
// Now the previous cache is lost
我建议为每个可能的参数值创建一个内存回调映射:
const genericSetLoadingCb = React.useMemo(() =>
{
[true]: () => setLoading(true),
[false]: () => setLoading(false),
},
[]
);
因此,每个值的记忆版本都是相同的:
return <button onClick={genericSetLoadingCb[!loading]}>{props.children}</button>