使用参数react useCallback

IT技术 javascript reactjs react-hooks callback
2021-03-28 01:34:03

使用 React 的useCallbackhook 本质上只是一个useMemo专门用于函数的包装器,以避免在组件的 props 中不断创建新的函数实例。我的问题来自何时需要将争论传递给从记忆创建的回调。

例如,像这样创建的回调......

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>
}

这将使记忆函数的目的落空,因为它仍然会在每次渲染时创建一个新函数,因为它也会在每次渲染时genericSetLoadingCb(false)返回一个新函数。

这种理解是否正确,或者用参数描述的模式是否仍然保持记忆的好处?

2个回答

动机和问题陈述

让我们考虑以下(类似于您的genericSetLoadingCb)高阶函数genericCb

  const genericCb = React.useCallback(
    (param) => (e) => setState({ ...state, [param]: e.target.value }),
    []
  );

genericCb可以在以下情况下有用Inputmemoized部件使用创建React.memo

  <Input value={state.firstName} onChange={genericCb('firstName')} />

由于Input是记忆化组件,我们可能希望生成的函数genericCb('firstName')在重新渲染时保持不变(以便记忆化组件不会不必要地重新渲染)。下面我们将看到如何实现这一点。

请注意,如果我们genericCb在这个例子中没有使用,并且我们有很多Input元素,我们需要为每个元素创建一个useCallback单独使用的事件处理程序

解决方案

现在,我们genericCb上面构造的方式是确保它在渲染中保持不变(由于使用了useCallback)。

但是,每次调用genericCb以创建一个新函数时,如下所示:

genericCb("firstName") 

返回的功能仍然会在每个渲染不同。为了确保返回的函数被某些 input 记住,你应该另外使用一些 memoizing 方法:

  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 收到一个依赖项未知的函数。改为传递内联函数

似乎警告在那里,因为我们传递memoizeuseCallback. 我根据这个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是行不通的,因为在下一次渲染时它会像这样从新调用 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
使用React.useMemo或内部React.useCallback而不是fast-memoize减少依赖会更好吗?
2021-05-24 01:34:03
@m_callens 我相信 useCallback 会记住你传递给它的函数,而不是你调用时创建的函数 genericSetLoadingCb
2021-06-05 01:34:03
在 TypeScript 中,它给了我们这个 eslint 警告:“React Hook useCallback 接收到一个依赖项未知的函数。改为传递一个内联函数。”。在依赖数组中放入什么而不是禁用 eslint 警告?
2021-06-10 01:34:03
@m_callens memoize 将为 memoizedcb(true) 和 memoizedcb(false) 创建不同的函数。你可以让 useCallback 依赖于这样的参数吗?
2021-06-15 01:34:03
好的,所以我假设仅React.useCallback在接收 HOF 的参数上使用不会提供任何value,除非您还在useCallback用法中记住内部函数
2021-06-19 01:34:03

执行以下操作似乎是解决问题的优雅而简单的方法。如果 Button 只是重新渲染,它不会创建新的 cb 函数。

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