1. ホーム
  2. reactjs

[解決済み] useCallbackとuseMemoは実際どう違うの?

2022-04-28 04:11:51

質問

もしかしたら何か勘違いしているかもしれませんが、useCallback Hookは再レンダリングが発生すると毎回実行されます。

私は、useCallbackの第2引数として、不変の定数であるinputsを渡しましたが、返されたメモ化コールバックは、レンダリングのたびに私の高価な計算を実行します(私は確信しています。)

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>

解決方法は?

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 の値です! パフォーマンスの問題ではなく、バグが発生してしまったのです! これは onPureChange へのアクセスにクロージャを使用しています。 a 変数が捕捉されたときに onPureChange が宣言されました。この問題を解決するには、React にどこで onPureChange を作成し、正しいデータを指す新しいバージョンを再作成/記憶(メモ化)します。これを行うには a として 依存関係 を `useCallback .' の第2引数に指定します。

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

さて、もし a が変更されると、Reactはコンポーネントを再レンダリングします。そして、再レンダリング中に onPureChange が異なるため、新しいバージョンのコールバックを再作成/メモライズする必要があります。ようやくすべてがうまくいきました。

NBだけでなく PureComponent / React.memo の依存関係として何かを使用する場合、参照性の等質性が重要になることがあります。 useEffect .