React useCallback with Parameter



使用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>

最新更新