useMemo和useCallback:何时使用它们,何时不使用它们

Posted suwu150

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了useMemo和useCallback:何时使用它们,何时不使用它们相关的知识,希望对你有一定的参考价值。

useMemo和useCallback:何时使用它们,何时不使用它们


我们都被告知要使用 React 钩子useMemouseCallback通过让 React “记住”复杂的计算来提高性能。每当我们将大量逻辑包装到这些钩子中时,它们真的能提高性能吗?

在本博客中,我们将了解这些钩子的作用并进行一些实验以了解它们对整体性能的影响。

useMemo和useCallback钩子是什么?它们是如何工作的?

useMemo,根据官方 React 文档,“返回一个记忆值”。一个使用示例是:

// sort elements in the `unsortedList`
const sortedList = useMemo(
  () => unsortedList.sort(),
  [unsortedList],
);

上面示例中的记忆化useMemo用于帮助提高性能。

在第一次渲染时,React 执行并“记住”该函数的返回值。在重新渲染中,React 使用 ‘remembered’ 值而不是再次执行函数(除非提供的依赖项数组中的值发生了变化,例如在上面的示例中unsortedList)。当函数的计算量很大时,不是每次渲染都执行它从而可以帮助我们提高性能。

在上面给出的示例unsortedList 中,React 不会每次都重新渲染,而是简单地记住第一次渲染的结果并再次使用它,前提是它unsortedList没有改变。

使用和不使用 useMemo() 的高级区别

useCallback工作原理类似。它不是记住执行函数的结果,而是记住提供的函数对象。这可能通过不必在每次状态更改时重新创建函数对象来提高性能。这在将函数传递给子组件时特别有用,因为它可以防止函数更改引起的重新渲染。

// function to call an API endpoint
const callApi = useCallback(
  () => fetch(`http://example.com/api/$userId`),
  [userId],
);

从上面的例子可以看出,callApi函数会被记忆。React 不会在每次渲染中重新创建这个函数,只要userId没有改变,React 就会重用相同的函数实例。

如果callApi函数没有被记忆并被传递到记忆的子组件中,那么每次父组件重新渲染时,子组件仍然会重新渲染,即使它被记忆了。这是因为父组件创建的函数每次都会不同。使用useCallback,只要其他道具没有改变,子组件(假设它已被记忆)就不会重新渲染。

...................................................................

简而言之

  • useMemo 用于记住值,减少重新渲染组件所需的时间。
  • useCallback 用于记住函数,通常是为了防止组件的重新渲染。

useMemo 并不总是有助于提高性能

这里有一个小实验,可以帮助我们确定这句话是否属实。通过对随机数数组进行排序,我们可以通过使用来突出代码逻辑复杂度与性能提升之间的关系。以下结果与Kevin 2019 年进行类似实验的一篇文章 useMemo.确实一致。

此设置运行了 2 个变体:

  • 有还是没有useMemo
  • 随机数数组的大小

这些测试在启用分析器的生产版本中运行,每个实验运行 3 次以获得平均结果。结果如下:

每个图表对应于一组具有相同数组大小 (100–1,000,000) 的实验。橙色列表示渲染时间useMemo,而黄色列表示没有。

注意:初始渲染时间包括渲染 html 元素和创建随机数组所花费的时间,但由于使用和不使用 useEffect 的时间相同,因此它仍然是一个公平的测试。

  • 显示使用和不使用 useMemo() 的渲染时间的图表



  • 突出了计算复杂度(数组大小)增加时的性能差异(以对数表示)


对于小型操作,useMemo确实会造成一些开销。useMemo随着计算复杂性的增加 - 特别是在 1,000+ 长度点之后,优势变得明显。

useMemo 和 useCallback 可能会降低您的代码的可读性

每个专业人士都有一个缺点,一个缺点是过度使用useMemo useCallback钩子可能会降低您的代码的可读性。它们不仅使代码库膨胀,而且您还必须积极维护依赖项数组,因为值或函数对象可能不会在您期望的时候更新。

查看以下将输入呈现unsortedList为排序列表的代码:

const ExampleComponent = ( unsortedList ) => 
  const sortedList = useMemo(() => 
    return unsortedList.sort();
  , [unsortedList]);

  return (
    <ul>
      sortedList.map((el) => (<li>el</li>))
    </ul>
  );
;

如果输入不是太长(例如 <200 个元素),我们已经在上面的部分证明了我们实际上不需要使用useMemo. 因此,我们可以这样做:

const ExampleComponent = ( unsortedList ) => (
  <ul>
    unsortedList.sort().map((el) => (
      <li>el</li>
    ))
  </ul>
);

这不仅使代码更易于阅读,而且当我们对提供给useMemo.

结论

useMemo和useCallback减少复杂计算中的重新渲染和渲染时间。但是,在某些情况下,弊大于利。下次您面临使用此解决方案时,请考虑利弊,尤其是在处理相对简单的计算时。


引用:
1.Should You Really Use useMemo in React? Let’s Find Out.
2.React ‘useMemo’ and ‘useCallback’: When to use them and when not
3.What are the benefits of using custom hooks in React Native?

以上是关于useMemo和useCallback:何时使用它们,何时不使用它们的主要内容,如果未能解决你的问题,请参考以下文章

useMemo和useCallback:何时使用它们,何时不使用它们

useMemo和useCallback:何时使用它们,何时不使用它们

这是对 useCallback 和 useMemo 的错误使用吗?

使用 React.memo、useCallback、useMemo 防止对象重新渲染

useMemo和useCallback的区别和使用

react——useMemo——useCallback——性能优化——React.memo