reactjs 在实践中,useCallback和useMemo有什么区别?

rjzwgtxy  于 2022-12-12  发布在  React
关注(0)|答案(7)|浏览(155)

也许我误解了什么,但是每次重新渲染时都会运行useCallbackHook。
我传递了inputs --作为useCallback的第二个参数--不可更改的常量--但是返回的memorized callback仍然在每次渲染时运行我的昂贵计算(我非常确定--您可以在下面的代码片段中自己检查)。
我已经将useCallback改为useMemo --useMemo如预期那样工作--在传递的输入发生变化时运行,并且真正记住了昂贵的计算。

示例:

第一个

cigdeys3

cigdeys31#

TL;DR;

  • useMemo用于存储函数调用之间和渲染之间的计算结果
  • useCallback是在渲染之间记忆回调本身(引用相等)
  • useRef用于保留渲染之间的数据(更新不会触发重新渲染)
  • useState用于保留渲染之间的数据(更新将触发重新渲染)
    长版本:

useMemo侧重于避免繁重的计算。
useCallback关注的是另一件事:它修复了onClick={() => { doSomething(...); }等内联事件处理程序导致PureComponent子级重新呈现时的性能问题(因为每次引用的函数表达式都不同)
也就是说,useCallback更接近于useRef,而不是存储计算结果的方式。
看了看文件,我同意这看起来很混乱。
useCallback将返回回调的记忆版本,该版本仅在其中一个输入发生更改时才发生更改。这在 * 将回调传递给依赖引用相等的优化子组件以防止不必要的呈现时 *(例如shouldComponentUpdate)非常有用。

示例

假设我们有一个基于PureComponent的子<Pure />,它只会在其props发生变化时重新呈现。
此代码在每次重新呈现父级时重新呈现子级,因为每次引用的内联函数都不同:

function Parent({ ... }) {
  const [a, setA] = useState(0);
  ... 
  return (
    ...
    <Pure onChange={() => { doSomething(a); }} />
  );
}

我们可以借助useCallback来处理这个问题:

function Parent({ ... }) {
  const [a, setA] = useState(0);
  const onPureChange = useCallback(() => {doSomething(a);}, []);
  ... 
  return (
    ...
    <Pure onChange={onPureChange} />
  );
}

但是一旦a被修改,我们发现我们创建的onPureChange处理函数--React为我们记住了--仍然指向旧的a值!我们得到的是一个bug而不是性能问题!这是因为onPureChange使用闭包来访问a变量,这是在声明onPureChange时捕获的。要解决此问题,我们需要让React知道在哪里删除onPureChange并重新创建/记住(memoize)指向正确数据的新版本。我们通过在'useCallback:

const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);

现在,如果a发生了变化,React会重新渲染<Parent>。在重新渲染过程中,它会发现onPureChange的依赖关系不同,因此需要重新创建/记忆一个新版本的回调。这将传递给<Pure>,由于它的引用不同,<Pure>也会被重新渲染。最后,一切都正常了!
注意:不仅仅对于PureComponent/React.memo,当在useEffect中使用某个对象作为依赖项时,引用相等性可能是关键。

vhmi4jdf

vhmi4jdf2#

useMemouseCallback使用记忆。
我喜欢把回忆当作记忆的东西。
虽然useMemouseCallback在依赖关系改变之前都记得渲染之间的一些东西,但区别只是它们记得的东西。
useMemo
记住您的函数返回的值。
useCallback
记住**您实际函数。
来源:What is the difference between useMemo and useCallback?

nlejzf6q

nlejzf6q3#

useCallbackuseMemo的单行程序:
useCallback(fn, deps)等同于useMemo(() => fn, deps)
使用useCallback可以记忆函数,而useMemo可以记忆任何计算值:

const fn = () => 42 // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]) // (1)
const memoFnReturn = useMemo(fn, [dep]) // (2)

(1)将返回fn的记忆版本--只要dep相同,多个渲染器之间的引用相同。但是,* 每次 * 调用 * memoFn时,复杂的计算都会重新开始。
(2)将在每次dep改变时调用fn,并记住它的 * 返回值 *(这里是42),然后将其存储在memoFnReturn中。
第一次

um6iljoc

um6iljoc4#

每次调用时,您都会调用记忆回调:

const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();

这就是useCallback的计数上升的原因。然而函数从未改变,它从未*****创建****新的回调,它总是一样的。这意味着useCallback正确地完成了它的工作。
让我们在代码中做一些更改来验证这一点。让我们创建一个全局变量lastComputedCallback,它将跟踪新如果返回了一个新的函数,这意味着useCallback只是“再次执行”。因此,当它再次执行时,我们将调用expensiveCalc('useCallback'),因为这是计算useCallback是否工作的方法。我在下面的代码中做了这件事,现在很明显useCallback正在按预期进行记忆。
如果你想看到useCallback每次都重新创建函数,那么取消数组中传递second的行的注解,你会看到它重新创建函数。
第一次
useCallback的好处是返回的函数是相同的,所以react不是每次都对元素执行removeEventListener 'ing和addEventListener ing,除非computedCallback发生变化。并且computedCallback只在变量发生变化时才发生变化。因此react只会对addEventListener执行一次。
问得好,回答了我学到了很多。

h6my8fg2

h6my8fg25#

useCallback()useMemo()几乎相同,但useCallback将函数引用保存在内存中,并在第二次渲染时检查是否相同。如果相同,则返回上次保存的函数,而不重新创建它。如果发生更改,则返回新函数,并将其替换为内存中的旧函数,以便将来渲染。useMemo以同样的方式工作,但它不能保存您的函数,只能保存计算返回值。在每次渲染时,useMemo都会检查该值。如果函数的返回值与第二次渲染时的值相同,则它将返回相同的值,而不重新计算函数值;如果第二次渲染时的值与第一次渲染时的值不同,则它将返回相同的值第二次渲染时,它将调用该函数并返回新值,然后将其存储以供将来渲染。

注意:当您需要使用这些挂接时必须小心。不必要地使用这些挂接可能会使您的应用程序性能变差,因为它们会占用内存。请确保如果您的组件多次重新呈现,并且计算量很大,则最好使用这些挂接。

rkkpypqq

rkkpypqq6#

在useMemo和useCallback中,钩子都接受一个函数和一个依赖项数组。

  • useMemo将内存返回的值,它缓存一个值类型。

用例:用于大量缓存计算值。

  • useCallback将存储函数,它缓存函数。

用例:用于缓存API调用方法,只能由用户的action调用。
干杯!

gstyhher

gstyhher7#

在详细了解useCallback和useMemo钩子之前,让我们先了解一下React如何比较在useEffect的依赖项数组中添加的值。
React通过使用Object.is(),即通过引用相等性来比较这些值。通常,对于基元数据类型,基于其值来比较值,如果它们相等,则认为它们相似,否则将它们视为不同,而对于非基元数据类型(对象、数组或函数),基于存储器位置引用来比较值,如果这些值共享相同的存储器位置,则它们被认为是相似的,否则被视为不同的(即使两个对象具有相同的属性,如果存储器位置引用不同,则它们被视为不相同)。
现在,如果useEffect的依赖关系依赖于基元数据类型值,那么就没有问题,因为我们已经看到React将如何比较它们。问题是非基元数据类型值,因为我们已经知道,对于相同的两个对象,内存位置引用相同,以便将它们视为相似。
现在问题来了,我们如何使两个相同的非原始数据值被认为是相似的。是的,唯一可能的答案是使它们共享相同的内存位置引用,现在我们实际上是如何做到这一点的呢?2这里有useCallback和useMemo钩子,如果渲染之间的值没有变化的话,它们可以帮助在相同的内存引用下存储非原始数据类型的值
现在,useEffect可以比较它的非原语依赖关系,并且只有在依赖关系中有实际变化时才运行。
如果函数用作useEffect中的依赖项,则该函数可被 Package 在返回记忆函数的useCallback中,即,返回存储在相同内存引用下的相同函数定义,除非函数依赖项未更改。

const memoizedFunc = useCallback(function useEffectDependentFunction(){ 
     return someValue; 
    }, [useEffectDependentFunction_dependency]) 
     
    // useEffectDependentFunction will be stored in the same memory location under the name memoizedFunc untill useEffectDependentFunction_dependency is not changed.

 
 
useEffect(()=>{ 
//do something 
}, [memoizedFunc]}

如果某个数组或对象或从函数中经过计算后获得的值将被用作useEffect中的依赖项,那么,涉及计算的函数可以被 Package 在useMemo中,useMemo根据用例返回记忆的数组或对象或值,与useCallback不同,它不会返回函数本身,而是返回函数的计算值。

const memoizedValue = useMemo(()=>function useEffectDependentValueYieldingFunction(){ 
//some calculation 
 return someValue; 
}, [useEffectDependentValueYieldingFunction_dependency]) 
 
// the value returned from the useEffectDependentValueYieldingFunction will be stored in the same memory location under the name memoizedValue untill useEffectDependentValueYieldingFunction_dependency is not changed. 
 
useEffect(()=>{ 
//do something 
}, [memoizedValue]}

就这样了
上面解释的场景是有效使用useCallback和useMemo的用例之一。

相关问题