我应该用 useCallback 还是 useMemo 包装每个道具,什么时候使用这个钩子?

Should I wrap every prop with useCallback or useMemo, when to use this hooks?

有了现在可用的反应钩子,我是否应该在功能组件的情况下用 useCallback and every other props value with useMemo 包装每个通过 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

另一种情况,如果在组件树中有许多组件记忆相同的值怎么办?这对性能有何影响?

不值得,原因有很多:

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

总而言之 - 如果您想将它们放在应用程序中,您将浪费更多的时间来输入所有挂钩,而不是让用户将它们放在应用程序中。古老的规则适用:测量,然后优化

同意@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. 做有意义的事情或衡量表现不佳的事情

我认为将 useCallback 放在你创建函数的任何地方都没有错。同样的要点也适用于useMemo;但为了简洁起见,我只会提到 useCallback

作为让步,我必须指出,这不是您开发过程中的基石,您必须继续采用您的团队满意的解决方案,无论是否使用 useCallback

我“广泛”使用此类记忆挂钩的主要论点是,如果您这样做,则不必考虑“参考 update-induced”重新呈现前的潜在性能问题。更少的事情要考虑 = 好。更多的时间和精力去解决实际问题。

很多人说“过早的优化不好”。好吧,货物崇拜也不好,这个引文就是一个纯粹的例子,太脱离上下文了,而且前提是有权威支持。 here 曾经是一个很好的总结,不幸的是,该帐户目前已被删除,但它可以在 archive.org 上使用。

在这种情况下,“过早优化”的问题是代码的结构更改和受损 readability/write-ability。

在结构上,这些变化是为了更好,让你分离你的组件。

Readability-wise,有一个额外的包装器,但它伴随着对函数依赖性的严格跟踪,否则它是隐式完成的。依赖关系变得明确,因为它们应该如此,因为在这个系统中,它们扮演的角色太重要了,不能掩盖它。因此,可读性只会获胜。现在虽难写,但“礼”始终如一。可读性比编写函数的便利性更重要。 (只要确保您使用 eslint 来跟踪您的依赖项;当您忘记添加依赖项并且它被缓存时,这可能会让人头疼。)

因此,“现在编写,稍后优化”- 谢谢,我会通过。对我来说,如果您的团队准备好接受上述论点,那么这是一个可以忽略不计的优化,足够合理且足够合理。如果他们不是,那也没关系:主题本身不是值得为之牺牲的东西。这只是一个小 quality-of-life 工具。