实际上,useCallback和useMemo有什么区别?

jav*_*ipt 38 reactjs

也许我误会了一些东西,但是useCallback Hook每次重新渲染时都会运行。

我传递了输入-作为useCallback的第二个参数-不可更改的常量-但返回的备忘录化回调在每次渲染时仍运行我的昂贵计算(我很确定-您可以在下面的代码段中自行检查)。

我已经将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')
);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

sky*_*yer 69

TL; DR;

  • useMemo 用来记住函数调用之间以及渲染之间的计算结果
  • useCallback 是要记住渲染之间的回调本身(引用相等)
  • useRef 是在渲染之间保留数据(更新不会触发重新渲染)
  • useState 是在渲染之间保留数据(更新将触发重新渲染)

长版:

useMemo 着重避免繁重的计算。

useCallback专注于不同的事物:它修复了内联事件处理程序(如onClick={() => { doSomething(...); } 使PureComponent子级重新渲染)时的性能问题(由于函数表达式每次都存在参照差异,因此)

这说useCallbackuseRef记忆计算结果的相当接近的方法。

查看文档,我确实同意它看起来令人困惑。

useCallback将返回一个记忆的回调版本,仅在输入之一发生更改时才更改。当将回调传递给依赖于引用相等性的优化子组件以防止不必要的渲染(例如,shouldComponentUpdate)时,此方法很有用。

假设我们有一个PureComponent基于子对象的子对象<Pure />,只有在其props更改后才会重新渲染

每次重新渲染父级时,下一个代码都会重新渲染子级-因为内联函数每次都参照不同

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

我们可以借助 useCallback

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

但是一旦a更改,我们会发现自己onPureChange创建了,React为我们记住的仍然指向旧的a价值!我们有一个错误而不是性能问题!这是因为onPureChange使用闭包功能来访问变量(而不是通过变量名访问)。为了使这项工作正确进行,我们需要让React知道在哪里删除onPureChange并重新创建/记住(记忆)指向正确数据的新版本。这里我们需要第二个参数:

const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Run Code Online (Sandbox Code Playgroud)

现在,如果a更改,React将重新渲染组件(useState此举)。在重新渲染期间,它会发现输入的数据onPureChange是不同的,因此需要重新创建/存储新版本的回调。终于,一切正常!


for*_*d04 39

一衬里useCallbackVS useMemo

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)
Run Code Online (Sandbox Code Playgroud)

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

(2)将在fn每次dep更改时调用并记住其返回值42此处),然后将其存储在memoFnReturn.

const fn = () => 42 // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]) // (1)
const memoFnReturn = useMemo(fn, [dep]) // (2)
Run Code Online (Sandbox Code Playgroud)
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"));
Run Code Online (Sandbox Code Playgroud)

  • 这一班轮的打击如此之大。谢谢 :) (2认同)

Wil*_*ger 22

useMemouseCallback使用记忆功能。

我喜欢把记忆看作是记住一些东西

虽然两者useMemouseCallback 记住之间呈现的东西,直到依赖关系的变化,不同的只是它们是什么记忆

useMemo记住函数的返回值。

useCallback记住你的实际功能。

来源:useMemo 和 useCallback 有什么区别?


Noi*_*art 11

每次执行此操作时,您每次都调用已记忆的回调:

const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
Run Code Online (Sandbox Code Playgroud)

这就是为什么数量useCallback增加的原因。但是该函数永远不会改变,它永远不会*****创建一个新的回调,它始终是相同的。意思useCallback是正确地完成了任务。

让我们在代码中进行一些更改以查看是否正确。让我们创建一个全局变量,lastComputedCallback该变量将跟踪是否返回了新的(不同的)函数。如果返回一个新函数,则意味着useCallback“再次执行”。因此,当它再次执行时,我们将调用expensiveCalc('useCallback'),因为这是您计算useCallback工作是否成功的方式。我在下面的代码中执行此操作,现在很明显,它正在useCallback按预期进行存储。

如果您希望useCallback每次都看到重新创建该函数,则取消注释通过的数组中的行second。您将看到它重新创建了功能。

const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
Run Code Online (Sandbox Code Playgroud)
'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')
);
Run Code Online (Sandbox Code Playgroud)

的好处useCallback是,返回的功能是相同的,所以反应不removeEventListener作“ing和addEventListener荷兰国际集团在元件每次,除非computedCallback改变。并且computedCallback唯一的改变是在变量改变时。因此反应只会发生addEventListener一次。

很好的问题,我通过回答中学到了很多东西。

  • 只是对好答案的一小段评论:主要目标不是关于`addEventListener / removeEventListener`(此操作本身并不繁重,因为不会导致DOM重排/重绘),而是避免重新呈现“ PureComponent”(或使用自定义的“ shouldComponentUpdate( )`)使用此回调的子级 (2认同)