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

也许我误解了什么,但是使用 Callback Hook 在每次重新渲染发生时都会运行。

我传递了输入-作为使用 Callback 的第二个参数-不可变的常量-但是返回的制表回调仍然在每次渲染时运行我昂贵的计算(我很确定-你可以在下面的代码片段中自己检查)。

我已经将 useCallback 改为 useMemo —— useMemo 按预期工作——在传递的输入发生变化时运行。而且真的记住了昂贵的计算。

实例:

'use strict';


const { useState, useCallback, useMemo } = React;


const neverChange = 'I never change';
const oneSecond = 1000;


function App() {
const [second, setSecond] = useState(0);
  

// This 👇 expensive function executes everytime when render happens:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
  

// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
  

setTimeout(() => setSecond(second + 1), oneSecond);
  

return `
useCallback: ${computedCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}


const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };


function expensiveCalc(hook) {
let i = 0;
while (i < tenThousand) i++;
  

return ++expensiveCalcExecutedTimes[hook];
}




ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

72699 次浏览

;

  • useMemo是在函数调用和呈现之间制表计算结果
  • useCallback是在呈现之间制表回调本身(引用相等)
  • useRef是在呈现之间保持数据(更新不触发重新呈现)
  • useState是保持渲染之间的数据(更新将触发重新渲染)

长话短说:

useMemo侧重于避免繁重的计算。

useCallback关注的是另一件事: 它修复了诸如 onClick={() => { doSomething(...); }这样的内联事件处理程序导致 PureComponent子级重新呈现时的性能问题(因为函数表达式每次都有参照不同)

也就是说,useCallback更接近于 useRef,而不是一种记录计算结果的方法。

看看 医生,我同意它看起来有点混乱。

useCallback将返回回调函数的制表版本,该版本只有在其中一个输入发生更改时才会更改。这是有用的 当传递回调到依赖引用相等性以防止不必要呈现的优化子组件时(例如,should dComponent entUpdate)。

例子

假设我们有一个基于 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,并重新创建/记住(制表)一个指向正确数据的新版本。我们通过在‘ useCallback:

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

现在,如果 a改变了,React 将重新呈现 <Parent>。在重新呈现过程中,它发现对 onPureChange的依赖是不同的,因此需要重新创建/制表一个新版本的回调函数。这被传递给 <Pure>,由于它的参考不同,<Pure>也被重新呈现。终于一切正常了!

注意,不仅对于 PureComponent/React.memo,当在 useEffect中使用某些东西作为依赖项时,引用相等性可能是至关重要的。

你每次打电话给备忘录回调,当你这样做的时候:

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

这就是 useCallback指数上升的原因。然而函数从不改变,它从不 * * * * * 创建 * * * * 一个新的回调函数,它总是相同的。这意味着 useCallback正在正确地完成它的工作。

让我们对代码进行一些修改,看看这是否正确。让我们创建一个全局变量 lastComputedCallback,它将跟踪是否返回一个新的(不同的)函数。如果返回一个新函数,那意味着 useCallback只是“再次执行”。所以当它再次执行时,我们将调用 expensiveCalc('useCallback'),因为这是如果 useCallback确实工作的计数方式。我在下面的代码中完成了这项工作,现在很明显,useCallback正在按照预期进行制表。

如果您希望每次都看到 useCallback重新创建函数,那么取消传递 second的数组中的行的注释。您将看到它重新创建函数。

'use strict';


const { useState, useCallback, useMemo } = React;


const neverChange = 'I never change';
const oneSecond = 1000;


let lastComputedCallback;
function App() {
const [second, setSecond] = useState(0);
  

// This 👇 is not expensive, and it will execute every render, this is fine, creating a function every render is about as cheap as setting a variable to true every render.
const computedCallback = useCallback(() => expensiveCalc('useCallback'), [
neverChange,
// second // uncomment this to make it return a new callback every second
]);
  

  

if (computedCallback !== lastComputedCallback) {
lastComputedCallback = computedCallback
// This 👇 executes everytime computedCallback is changed. Running this callback is expensive, that is true.
computedCallback();
}
// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
  

setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${expensiveCalcExecutedTimes.useCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}


const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };


function expensiveCalc(hook) {
let i = 0;
while (i < 10000) i++;
  

return ++expensiveCalcExecutedTimes[hook];
}




ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

useCallback的好处是返回的函数是相同的,所以反应不是 removeEventListener‘ ing 和 addEventListenering 的元素每次,除非 computedCallback的变化。而且 computedCallback只有在变量改变时才会改变。因此反应将只有 addEventListener一次。

问得好,回答这个问题我学到了很多。

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)

只要 dep是相同的,(1)将返回一个制表版本的 fn-相同的引用跨多个渲染。但是 每次都是调用 memoFn,那个复杂的计算又开始了。

(2)将在每次 dep改变时调用 fn,并记住它的 返回值(这里是 42) ,然后将其存储在 memoFnReturn中。

const App = () => {
const [dep, setDep] = useState(0);
const fn = () => 42 + dep; // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]); // (1)
const memoFnReturn = useMemo(fn, [dep]); // (2)


return (
<div>
<p> memoFn is {typeof memoFn} </p>
<p>
Every call starts new calculation, e.g. {memoFn()} {memoFn()}
</p>
<p>memoFnReturn is {memoFnReturn}</p>
<p>
Only one calculation for same dep, e.g. {memoFnReturn} {memoFnReturn}
</p>
<button onClick={() => setDep((p) => p + 1)}>Change dep</button>
</div>
);
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback, useMemo } = React</script>

useMemouseCallback使用制表。

我喜欢 记忆就是记住一些东西

虽然 useMemouseCallback 记住之间的东西渲染,直到依赖性改变,差异只是什么他们 记住

useMemo记住从函数返回的值。

useCallback记住你的实际功能。

资料来源: UseMemo 和 useCallback 的区别是什么?

useCallback()useMemo()大致相同,但是 useCallback将函数引用保存在内存中,并在第二次呈现时检查它是否相同,如果相同,则返回上次保存的函数,而不重新创建它,如果它被更改,则返回一个新函数,并在内存中用较旧的函数替换它,以便将来呈现。 useMemo的工作方式相同,但它不能保存你的功能,但 计算出来的回来了值。在每次渲染时,useMemo都会检查函数的返回值是否在第二次渲染时相同,然后它将返回相同的值,而不会重新计算函数值。如果在第二次渲染时返回的值不相同,它将调用函数并返回新的值,并将其存储以备将来渲染时使用。

注意: 当你需要使用这些钩子时,你必须小心。不必要地使用这些钩子会使应用程序的性能变差,因为它们使用内存。确保如果您的组件多次使用繁重的计算重新呈现,那么最好使用这些钩子。

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

  • UseMemo 将存储返回的值,它缓存一个值类型。

用例: 大量使用它缓存计算值。

  • UseCallback 将内存函数,它缓存一个函数。

用例: 用于缓存 API 调用方法,该方法只由 使用者的行为。

干杯!