useMemo和useEffect + useState的区别

207
useMemo与使用useEffectuseState的组合,是否有任何好处(例如用于密集函数调用)?这里有两个自定义钩子,在第一眼看起来完全相同,除了在第一次渲染时useMemo返回值为null在 CodeSandbox 上查看

useEffect & useState

import { expensiveCalculation } from "foo";

function useCalculate(someNumber: number): number | null {
  const [result, setResult] = useState<number | null>(null);

  useEffect(() => {
    setResult(expensiveCalculation(someNumber));
  }, [someNumber]);

  return result;
}

useMemo

import { expensiveCalculation } from "foo";

function useCalculateWithMemo(someNumber: number): number {
    return useMemo(() => {
        return expensiveCalculation(someNumber);
    }, [someNumber]);
};

每当它们的参数someNumber更改时,两者都会计算结果,那么useMemo的记忆化何时开始发挥作用?


1
第一个在第一次渲染时将为 null,而第二个则不会? - Jonas Wilms
1
使用useMemo有什么好处(例如用于密集的函数调用)吗?是的。您正在使用专门为此目的设计的钩子。您列出的示例是useMemo最常见的现实世界示例。 - Estus Flask
3个回答

208
useEffectsetState在每次更改时都会导致额外的渲染:第一次渲染将会使用旧数据,然后它将立即排队等待一个带有新数据的附加渲染。

假设我们有:
// Maybe I'm running this on a literal potato
function expensiveCalculation(x) { return x + 1; };

假设x最初为0:

  • useMemo版本会立即呈现1
  • useEffect版本呈现null,然后在组件呈现后运行效果,改变状态并排队新的渲染与1

然后,如果我们将x更改为2:

  • useMemo运行,呈现3
  • useEffect版本运行,并再次呈现1,然后效果触发,组件重新运行,具有3的正确值。

expensiveCalculation运行频率而言,两者具有相同的行为,但是useEffect版本会引起两倍的渲染,这对性能有其他方面的不利影响。

此外,个人认为useMemo版本更清晰易读。它不会引入不必要的可变状态,并且移动部分较少。

因此,在这里最好只使用useMemo


36
我认为即使在某些长时间运行的同步场景中,useEffect也可能会很有用。请查看下面的沙盒。由于useMemo在长时间计算运行时阻塞了渲染线程,因此需要5秒钟才能加载;而使用useEffect和useState可以在计算运行时显示“旋转图标”,从而不会阻塞渲染: https://codesandbox.io/s/usememo-vs-useeffect-usestate-ye6qm@Retsam - Mark Adamson
6
除了优化之外,我使用useMemo而不是useState+ useEffect模式,因为渲染次数增多时调试更加困难。 - ecoe
6
值得注意的是,React API文档提到useMemo不能保证如果依赖项未更改则不会再次执行已记忆的函数,因为React未来可能会丢弃缓存以提高性能。因此,如果记忆的函数具有某种副作用,则使用自定义钩子可能更明智。 - M Miller
3
改变属性会触发重新渲染。但是渲染的值基于 [result, setResult] 状态,而 setResult 直到 useEffect 运行之后才会被调用,也就是在渲染之后。 - Retsam
8
从这个问题可以得出结论,当要计算的事情是异步的时,使用 useEffect + useState 是正确的解决方案,因为该值在当前渲染中不可用。相关问题请参考链接 - bluenote10
显示剩余10条评论

26

在选择它们之间时,我认为你应该考虑两个主要因素。

  1. 函数调用的时间。

useEffect 在组件渲染后被调用,因此您可以从中访问 DOM。例如,如果您想通过引用访问 DOM 元素,则这一点非常重要。

  1. 语义保证。

useEffect 保证如果依赖项未更改,则不会触发它。而 useMemo 不提供这样的保证。

正如在React 文档中所述,您应将 useMemo 视为纯优化技术。即使您将 useMemo 替换为常规函数调用,您的程序也应继续正确工作。

useEffect + useState 可以用于控制更新,甚至打破循环依赖关系并防止无限更新循环。


2
useEffect并不保证在依赖项未改变时不会重新渲染。useMemo是不会在依赖项未改变时触发的函数。我从来没有遇到过在useMemo上出现额外重渲染的问题,但有时在useEffect上即使依赖项未改变也会重新渲染。相反,需要注意的是useMemo难以检测一些复杂结构的依赖项中的变化(例如一个数组中的对象变化)。 - GiselleMtnezS

19

除了异步性质之外,它们在设计上可能存在一些差异。

useEffect 是一个集体调用,无论异步与否,在所有组件渲染完成后进行调用。

useMemo 是一个本地调用,仅与该组件有关。您可以将 useMemo 视为具有使用上一次更新的赋值语句的好处的另一种赋值语句。

这意味着,useMemo 更加紧急,然后是 useLayoutEffect,最后是 useEffect


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接