【问题标题】:Should I wrap every prop with useCallback or useMemo, when to use this hooks?我应该用 useCallback 或 useMemo 包装每个道具,什么时候使用这个钩子?
【发布时间】:2019-08-14 02:14:46
【问题描述】:

如果功能组件现在可以使用 react 钩子,我是否应该使用 useCallback 包装使用 props 传递的每个函数,并使用 useMemo 包装所有其他 props 值?

我的组件内部还具有依赖于任何 props 值的自定义函数,我应该用 useCallback 包装它吗?

有什么好的做法来决定组件中的哪些 props 或 const 值用这个钩子包装?

如果这样可以提高性能,为什么不一直这样做呢?

让我们考虑包装点击处理程序并添加自定义逻辑的自定义按钮

function ExampleCustomButton({ onClick }) {
  const handleClick = useCallback(
    (event) => {
      if (typeof onClick === 'function') {
        onClick(event);
      }

      // do custom stuff

    },
    [onClick]
  );

  return <Button onClick={handleClick} />;
}

让我们考虑自定义按钮,我们在其中包装点击处理程序并根据条件添加自定义逻辑

function ExampleCustomButton({ someBool }) {
  const handleClick = useCallback(
    (event) => {
      if (someBool) {
        // do custom stuff
      }
    },
    [someBool]
  );

  return <Button onClick={handleClick} />;
}

在这两种情况下我应该用useCallback 包裹我的处理程序吗?

与使用备忘录类似的情况。

function ExampleCustomButton({ someBool }) {
  const memoizedSomeBool = useMemo(() => someBool, [someBool])
  const handleClick = useCallback(
    (event) => {
      if (memoizedSomeBool) {
        // do custom stuff
      }
    },
    [memoizedSomeBool]
  );

  return <Button onClick={handleClick} />;
}

在这个例子中,我什至将记忆值传递给useCallback

如果在组件树中许多组件记住相同的值,会怎样?这对性能有何影响?

【问题讨论】:

  • 嗯,绝对不是每次,所有时间:) 它们的使用是根据情况而定的。当您开始遇到性能障碍时,我会说考虑实际使用这些。
  • 感谢@Powell_v2 的评论,但如果这能提高性能,为什么不一直这样做呢?
  • 我想如果这可行的话,React 会自动处理它,而不是将这些作为单独的 API 公开。正如@jalooc 指出的那样,记忆回调/值是有成本的。代码混乱也是一个问题。
  • 如果点击处理程序不使用其中任何组件的props,那么您可以在组件外部声明它,然后处理程序函数不会在组件的每个 prop 更改时重新创建(因为它在外面..)
  • 还应该补充一点,将useMemo放在useCallback中是无效的。 useMemo 只能在 React 函数组件或自定义 React 钩子中调用。

标签: reactjs react-hooks


【解决方案1】:

不值得,原因有很多:

  1. 甚至官方文档都说您应该只在必要时才这样做。
  2. 请记住,过早的优化是万恶之源 :)
  3. 它使 DX(开发人员体验)变得更糟:它更难阅读;更难写;更难重构。
  4. 在处理原语(如您的示例中)时,记忆比不记忆消耗更多的 CPU 功率。原始值没有 references 的概念,因此其中没有什么可记忆的。另一方面,记忆本身(与任何其他挂钩)确实需要一些微小的处理,没有什么是免费的。尽管它很小,但它仍然比什么都没有(与只是通过一个图元相比),所以你会用这种方法射击自己的脚。

综合起来——如果你想把它们放在任何地方,你会浪费更多的时间来输入所有的钩子,而不是用户在应用程序中获得它们。良好的旧规则适用:衡量,然后优化

【讨论】:

  • “仅在必要时执行”没有回答任何问题。什么时候是“必要的”?将 prop 的值更改为重新创建的函数将重新渲染子组件。所以我想这取决于重新渲染受影响的子组件的成本与使用useCallback() 的成本。
  • “必要时”在以下段落中进行了描述,其中说您应该在测量后将其包裹起来,并发现特定点是缓慢的根源。
  • 引用的一部分,带有笑脸。至少完成它:但我们不应该放弃那关键的 3% 的机会。 An excellent article on this.
【解决方案2】:

我同意@jalooc 提出的原则

为了更深入地了解 OP 中展示的用例,以下是我的建议:

昂贵的子渲染

function Component() {
  const callback = useCallback(() => { dostuff }, [deps])

  return <Child prop={callback} />
}

如果Child 是一个渲染成本非常高的组件,则上述内容是有意义的。因此,它可能是这样导出的:

function Child() { 
   ...this takes significant CPU... 
}

// Export as a pure component
export default React.memo(Child)

昂贵的计算

function Component({ foo }) {
  // This very expensive computation will only run when it's input (foo)
  // changes, allowing Component to re-render without performance issues
  const bar = useMemo(() => {
     ... something very complicated with `foo` ...
  }, [foo])

  return <div>{bar}</div>
}

结论

  1. 做有意义的事或衡量表现不佳的事
  2. 组件内的函数声明在每次渲染时都会发生变化。如果这导致派生的计算量很大,请将其记下来 (useCallback) 或将其移出范围。
  3. 如果组件本身的渲染成本很高,请使用React.memo 使其成为纯组件,如有必要,请在#2 的帮助下进行
  4. 如果某些东西本身重新计算的成本很高,请记住它 (useMemo)
  5. 做有意义的事或衡量表现不佳的事

【讨论】:

  • 另外,useMemo 在将渲染阶段创建的对象传递到使用React.memo 包裹的组件时是必需的
  • 这适用于在父组件中创建的所有对象,传递给记忆组件,也适用于所有回调/函数,应该被记忆 - 在这种情况下,在useCallback 的帮助下。基线,如果 memo-wrapped 组件的 props 变化太频繁,那么 memo 将不会很有用。谢谢提醒。
猜你喜欢
  • 2022-12-18
  • 2021-05-31
  • 2016-05-09
  • 2021-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多