useMemo は、再レンダリング間の計算結果をキャッシュできるReactフックです。

const cachedValue = useMemo(calculateValue, dependencies)

参照

useMemo(calculateValue, dependencies)

コンポーネントの最上位レベルでuseMemoを呼び出して、再レンダリング間の計算をキャッシュします。

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}

下記にさらに例を示します。

パラメータ

  • calculateValue: キャッシュする値を計算する関数。ピュア関数でなければならず、引数を取らず、任意の型の値を返す必要があります。Reactは最初のレンダリング時にこの関数を呼び出します。次のレンダリングでは、dependenciesが前回のレンダリング以降に変更されていない場合、同じ値を再び返します。それ以外の場合は、calculateValueを呼び出し、その結果を返し、後で再利用できるように保存します。

  • dependencies: calculateValueコード内で参照されるすべてのリアクティブ値のリスト。リアクティブ値には、props、state、コンポーネント本体内で直接宣言されたすべての変数と関数が含まれます。リンターがReact用に設定されている場合、すべてのリアクティブ値が依存関係として正しく指定されていることを確認します。依存関係のリストは、一定数の項目を持ち、[dep1, dep2, dep3]のようにインラインで記述する必要があります。Reactは、Object.is比較を使用して、各依存関係とその前の値を比較します。

戻り値

最初のレンダリングでは、useMemoは引数なしでcalculateValueを呼び出した結果を返します。

次のレンダリングでは、前回のレンダリングからの既に保存されている値を返すか(依存関係が変更されていない場合)、calculateValueを再度呼び出して、calculateValueが返した結果を返します。

注意事項

  • useMemoはHookなので、コンポーネントの最上位レベル、または独自のHook内でのみ呼び出すことができます。ループや条件分岐内では呼び出すことができません。そのような必要がある場合は、新しいコンポーネントを作成して状態をそこに移動してください。
  • Strict Modeでは、Reactは偶発的な不純物を検出するのを助けるために、計算関数を2回呼び出します。これは開発時のみの動作であり、本番環境には影響しません。計算関数が純粋関数である場合(そうあるべきですが)、これはロジックに影響を与えません。呼び出しのいずれかからの結果は無視されます。
  • Reactはキャッシュされた値を、特別な理由がない限り破棄しません。例えば、開発中は、コンポーネントのファイルを編集すると、Reactはキャッシュを破棄します。開発時と本番時において、初期マウント中にコンポーネントが中断した場合、Reactはキャッシュを破棄します。将来、Reactはキャッシュの破棄を利用する機能を追加する可能性があります。例えば、将来的にReactが仮想リストの組み込みサポートを追加する場合、仮想化されたテーブルのビューポートからスクロールアウトしたアイテムのキャッシュを破棄することが理にかなっています。useMemoをパフォーマンス最適化のみに依存する場合は問題ありません。そうでない場合は、状態変数またはrefの方が適切な場合があります。

注意

このような戻り値のキャッシュは、メモ化としても知られており、このHookがuseMemoと呼ばれる理由です。


使用方法

高価な再計算のスキップ

再レンダリング間の計算をキャッシュするには、コンポーネントの最上位レベルでuseMemo呼び出しでラップします。

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

useMemoには2つの引数を渡す必要があります。

  1. 引数を取らず、() =>のように、計算結果を返す計算関数です。
  2. 計算で使用されるコンポーネント内のすべての値を含む依存関係のリストです。

最初のレンダリングでは、useMemoから取得するは、計算関数を呼び出した結果になります。

以降のレンダリングでは、Reactは依存関係を前回のレンダリング時に渡した依存関係と比較します。依存関係のいずれも変更されていない場合(Object.isで比較)、useMemoは既に計算した値を返します。そうでない場合、Reactは計算を再実行し、新しい値を返します。

つまり、useMemoは、依存関係が変更されるまで、再レンダリング間の計算結果をキャッシュします。

これがいつ役立つのかを例を通して見ていきましょう。

デフォルトでは、Reactは再レンダリングするたびにコンポーネントの本体全体を再実行します。TodoListが状態を更新するか、親から新しいpropsを受け取ると、filterTodos関数が再実行されます。

function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}

通常、これは問題になりません。ほとんどの計算は非常に高速だからです。しかし、大規模な配列をフィルタリングまたは変換したり、高価な計算を実行したりする場合、データが変更されていない場合は再実行をスキップしたいと思うかもしれません。todostabの両方が前回のレンダリング時と同じである場合、先に示したように計算をuseMemoでラップすると、既に計算済みのvisibleTodosを再利用できます。

このタイプのキャッシングは、メモ化と呼ばれます。

注意

useMemoはパフォーマンス最適化としてのみ使用する必要があります。これがないとコードが動作しない場合は、根本的な問題を見つけて先に修正してください。その後、パフォーマンスを向上させるためにuseMemoを追加できます。

詳細

計算が高価かどうかを判断する方法

一般的に、数千個のオブジェクトを作成したり、ループ処理を行ったりしない限り、コストはそれほど高くありません。より確信を得たい場合は、コンソールログを追加してコードの実行時間を測定できます。

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

測定する操作(たとえば、入力への文字入力)を実行します。すると、コンソールにfilter array: 0.15msのようなログが表示されます。ログに記録された合計時間が大幅な時間(たとえば1ms以上)に達する場合は、その計算をメモ化することが理にかなっているかもしれません。実験として、useMemoで計算をラップして、その操作のログに記録された合計時間が減少したかどうかを確認できます。

console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');

useMemoは、最初のレンダリングを高速化しません。更新時の不要な作業をスキップするのに役立ちます。

ご自身のマシンはユーザーのマシンよりも高速である可能性が高いので、人工的な遅延を伴ってパフォーマンスをテストすることをお勧めします。たとえば、Chromeには、この目的でCPUスロットリングオプションがあります。

また、開発環境でのパフォーマンス測定では、最も正確な結果が得られないことに注意してください。(たとえば、StrictModeが有効になっている場合、各コンポーネントは1回ではなく2回レンダリングされます。)最も正確なタイミングを得るには、アプリケーションを本番環境向けにビルドし、ユーザーと同じデバイスでテストしてください。

詳細

すべての場所にuseMemoを追加する必要がありますか?

アプリケーションがこのサイトのようなものであり、ほとんどの操作が粗粒度(ページ全体またはセクション全体の置き換えなど)である場合、メモ化は通常不要です。一方、アプリケーションが図面エディターのようなものであり、ほとんどの操作が微細粒度(図形の移動など)である場合、メモ化は非常に役立つ場合があります。

useMemoによる最適化は、少数のケースでのみ価値があります。

  • useMemoに入れている計算が著しく遅く、その依存関係がめったに変更されない場合。
  • memoでラップされたコンポーネントにプロップとして渡します。値が変更されていない場合は、再レンダリングをスキップしたいと考えています。メモ化により、依存関係が同じでない場合にのみコンポーネントが再レンダリングされます。
  • 渡している値は後で、いくつかのフックの依存関係として使用されます。たとえば、別のuseMemo計算値がそれに依存している可能性があります。あるいは、useEffectからこの値に依存している可能性もあります。

他のケースでは、計算をuseMemoでラップすることには利点がありません。それを行うことにも大きな害はありません。そのため、一部のチームは個々のケースを考えずに、できるだけ多くのメモ化を行うことを選択しています。このアプローチの欠点は、コードの可読性が低下することです。また、すべてのメモ化が効果的であるとは限りません。「常に新しい」単一の値があれば、コンポーネント全体のメモ化が中断される可能性があります。

実際には、いくつかの原則に従うことで、多くのメモ化を不要にすることができます。

  1. コンポーネントが視覚的に他のコンポーネントをラップする場合、JSXを子として受け入れるようにします。これにより、ラッパーコンポーネントが独自のステートを更新すると、Reactはその子コンポーネントを再レンダリングする必要がないことを認識します。
  2. ローカルステートを優先し、必要以上にステートを上に持ち上げるのは避けてください。たとえば、フォームやアイテムがホバーされているかどうかなどの一時的なステートは、ツリーの一番上またはグローバルステートライブラリに保持しないでください。
  3. レンダリングロジックをピュアに保つ。コンポーネントの再レンダリングが問題を引き起こしたり、目に見えるアーティファクトを作成したりする場合は、コンポーネントのバグです!メモ化を追加する代わりに、バグを修正してください。
  4. ステートを更新する不要なエフェクトを避ける。Reactアプリケーションのパフォーマンスの問題のほとんどは、コンポーネントを何度もレンダリングさせるエフェクトから発生する更新の連鎖によって引き起こされます。
  5. エフェクトから不要な依存関係を削除するようにしてください。たとえば、メモ化の代わりに、オブジェクトまたは関数をエフェクト内またはコンポーネントの外に移動する方が簡単な場合が多いです。

特定の操作がまだ遅いと感じる場合は、React Developer Toolsプロファイラーを使用して、どのコンポーネントがメモ化によって最も恩恵を受けるかを調べ、必要に応じてメモ化を追加します。これらの原則は、コンポーネントのデバッグと理解を容易にするため、いずれの場合でも従うのが良いでしょう。長期的に見ると、微細粒度のメモ化を自動的に行うことで、この問題を完全に解決するための研究を行っています。

useMemoと値の直接計算の違い

1 2:
useMemoによる再計算のスキップ

この例では、filterTodosの実装は人工的に遅延されています。そのため、レンダリング中に呼び出しているJavaScript関数が実際に遅い場合に何が起こるかを確認できます。タブを切り替え、テーマを切り替えてみてください。

タブの切り替えは遅いように感じます。これは、遅延したfilterTodosを強制的に再実行するためです。tabが変更されたため、計算全体を再実行する必要があります。(それが2回実行される理由が気になる場合は、こちらで説明されています。)

テーマを切り替えます。useMemoのおかげで、人工的な遅延にもかかわらず高速です!todostabの両方(useMemoへの依存関係として渡すもの)が前回のレンダリング以降変更されていないため、遅いfilterTodos呼び出しはスキップされました。

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


コンポーネントの再レンダリングスキップ

場合によっては、useMemoを使用すると、子コンポーネントのレンダリングのパフォーマンスを最適化することもできます。例として、このTodoListコンポーネントがvisibleTodosをプロップとして子Listコンポーネントに渡すとします。

export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}

themeプロップを切り替えるとアプリが一瞬フリーズすることに気づいたと思いますが、<List />をJSXから削除すると高速になります。これはListコンポーネントの最適化を試みる価値があることを示しています。

デフォルトでは、コンポーネントが再レンダリングされると、Reactはすべての子を再帰的に再レンダリングします。そのため、TodoListが異なるthemeで再レンダリングされると、Listコンポーネントも再レンダリングされます。これは、再レンダリングに多くの計算を必要としないコンポーネントには問題ありません。しかし、再レンダリングが遅いことを確認した場合は、memoでラップすることで、プロップが前回のレンダリングと同じ場合にListに再レンダリングをスキップするように指示できます。

import { memo } from 'react';

const List = memo(function List({ items }) {
// ...
});

この変更により、Listは、そのすべてのプロップが前回のレンダリングと同じである場合に再レンダリングをスキップします。ここでキャッシュされた計算が重要になります!useMemoなしでvisibleTodosを計算した場合を考えてみてください。

export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
}

上記の例では、filterTodos関数は常に異なる配列を作成します。これは{}オブジェクトリテラルが常に新しいオブジェクトを作成する方法に似ています。通常、これは問題ではありませんが、Listのプロップは決して同じにならず、memoの最適化が機能しません。ここでuseMemoが役立ちます。

export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
}

visibleTodosの計算をuseMemoでラップすることにより、再レンダリング間で同じ値を持つようにします(依存関係が変わるまで)。 特定の理由がない限り、useMemoで計算をラップする必要はありません。この例では、memoでラップされたコンポーネントに渡すためであり、これにより再レンダリングをスキップできます。useMemoを追加する他のいくつかの理由があり、このページでさらに説明されています。

詳細

個々のJSXノードのメモ化

memoListをラップする代わりに、<List />JSXノード自体をuseMemoでラップできます。

export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}

動作は同じです。visibleTodosが変更されていない場合、Listは再レンダリングされません。

<List items={visibleTodos} />のようなJSXノードは{ type: List, props: { items: visibleTodos } }のようなオブジェクトです。このオブジェクトの作成は非常に安価ですが、Reactはその内容が前回と同じかどうかを知りません。そのため、デフォルトでは、ReactはListコンポーネントを再レンダリングします。

ただし、Reactが前回のレンダリング時と同じJSXを検出した場合、コンポーネントの再レンダリングは試みません。これは、JSXノードが不変であるためです。JSXノードオブジェクトは時間の経過とともに変化することはないため、Reactは再レンダリングをスキップしても安全であることを認識しています。ただし、これが機能するには、ノードが実際に同じオブジェクトである必要があり、コード上では単に同じに見えるだけでは不十分です。これが、この例でuseMemoが行うことです。

JSXノードを手動でuseMemoにラップするのは便利ではありません。たとえば、これを条件付きで行うことはできません。通常、これがJSXノードをラップするのではなく、コンポーネントをmemoでラップする理由です。

再レンダリングのスキップと常に再レンダリングの違い

1 2:
useMemomemoを使った再レンダリングのスキップ

この例では、Listコンポーネントは人為的に速度を落とされています。これにより、レンダリングしているReactコンポーネントが実際に遅い場合に何が起こるかを確認できます。タブを切り替えたり、テーマを切り替えたりしてみてください。

タブの切り替えが遅いのは、速度を落としたListの再レンダリングを強制するためです。tabが変更されたため、ユーザーの新しい選択を画面に反映する必要があるため、これは予想される動作です。

次に、テーマの切り替えを試みてください。useMemomemoのおかげで、人為的な速度低下にもかかわらず、高速です! Listは、visibleTodos配列が前回のレンダリング以降変更されていないため、再レンダリングをスキップしました。visibleTodos配列が変更されていないのは、todostabuseMemoへの依存関係として渡す)の両方が前回のレンダリング以降変更されていないためです。

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>List</code> is artificially slowed down!</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


Effectの発火頻度の抑制

場合によっては、Effect内で値を使用したい場合があります。

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = {
serverUrl: 'https://localhost:1234',
roomId: roomId
}

useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ...

これにより問題が発生します。すべてのリアクティブな値は、Effectの依存関係として宣言する必要があります。 しかし、optionsを依存関係として宣言すると、Effectがチャットルームに継続的に再接続される原因になります。

useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // 🔴 Problem: This dependency changes on every render
// ...

これを解決するには、Effectから呼び出す必要があるオブジェクトをuseMemoでラップできます。

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = useMemo(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes

useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Only changes when createOptions changes
// ...

これにより、useMemoがキャッシュされたオブジェクトを返す場合、optionsオブジェクトは再レンダリング間で同じになります。

しかし、useMemoはパフォーマンスの最適化であり、セマンティックな保証ではないため、特定の理由がある場合、Reactはキャッシュされた値を破棄することがあります。これにより、Effectも再発火するため、関数依存関係の必要性をなくすことがさらに優れています。オブジェクトをEffectの内部に移動することで実現できます。

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
const options = { // ✅ No need for useMemo or object dependencies!
serverUrl: 'https://localhost:1234',
roomId: roomId
}

const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...

これで、コードはシンプルになり、useMemoは必要なくなりました。Effectの依存関係の削除について詳しく学ぶ。

別のフックの依存関係のメモ化

コンポーネント本体で直接作成されたオブジェクトに依存する計算があるとします。

function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...

このようなオブジェクトに依存すると、メモ化の目的が損なわれます。コンポーネントが再レンダリングされると、コンポーネント本体内のすべてのコードが再び実行されます。searchOptionsオブジェクトを作成するコード行も、毎回再実行されます。 searchOptionsuseMemo呼び出しの依存関係であり、毎回異なるため、Reactは依存関係が異なることを認識し、searchItemsを毎回再計算します。

これを修正するには、依存関係として渡す前にsearchOptionsオブジェクト自体をメモ化できます。

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ...

上記の例では、textが変更されなかった場合、searchOptionsオブジェクトも変更されません。ただし、さらに優れた修正方法は、searchOptionsオブジェクトの宣言をuseMemo計算関数の内部に移動することです。

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ...

これで、計算はtextに直接依存するようになりました(これは文字列であり、「誤って」異なるようになることはありません)。


関数のメモ化

memoでラップされたFormコンポーネントがあるとします。プロップとして関数を渡したいと考えています。

export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}

return <Form onSubmit={handleSubmit} />;
}

{}が異なるオブジェクトを作成するのと同じように、function() {}() => {}のような関数宣言は、毎回異なる関数を生成します。それ自体では、新しい関数を作成することは問題ではありません。これは避けるべきことではありません!ただし、Formコンポーネントがメモ化されている場合、おそらくプロップが変更されていない場合は再レンダリングをスキップしたいと考えています。常に異なるプロップは、メモ化の目的を無効にします。

useMemoで関数をメモ化するには、計算関数が別の関数を返す必要があります。

export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

これはぎこちなく見えます!関数のメモ化はReactにそのための組み込みフックがあるほど一般的です。関数をuseCallbackでラップし、追加のネストされた関数を作成する必要性を回避してください。useMemoの代わりに

export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

上記の2つの例は完全に同等です。useCallbackの唯一の利点は、追加のネストされた関数の記述を回避できることです。それ以外は何も行いません。useCallbackの詳細を読む


トラブルシューティング

私の計算は、再レンダリングごとに2回実行されます

厳格モードでは、Reactは一部の関数を1回ではなく2回呼び出します。

function TodoList({ todos, tab }) {
// This component function will run twice for every render.

const visibleTodos = useMemo(() => {
// This calculation will run twice if any of the dependencies change.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

これは想定された動作であり、コードを壊すことはありません。

この開発時のみの動作は、コンポーネントをピュアに保つのに役立ちます。Reactは呼び出しの1つの結果を使用し、もう1つの結果を無視します。コンポーネントと計算関数がピュアである限り、これはロジックに影響を与えません。ただし、誤って不純なものである場合、これにより間違いに気づき、修正することができます。

たとえば、この不純な計算関数は、プロップとして受け取った配列を書き換えます。

const visibleTodos = useMemo(() => {
// 🚩 Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

Reactは関数を2回呼び出すため、todoが2回追加されたことに気づきます。計算では既存のオブジェクトを変更するべきではありませんが、計算中に作成した *新しい* オブジェクトは変更しても問題ありません。たとえば、filterTodos関数が常に *異なる* 配列を返す場合、その配列を書き換えることができます。

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correct: mutating an object you created during the calculation
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);

純粋性について詳しくはコンポーネントをピュアに保つを読んでください。

また、オブジェクトの更新配列の更新に関するガイドも参照してください。(変更せずに)


私のuseMemo呼び出しはオブジェクトを返すはずですが、undefinedを返します

このコードは動作しません。

// 🔴 You can't return an object from an arrow function with () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

JavaScriptでは、() => {はアロー関数の本体を開始するため、{中括弧はオブジェクトの一部ではありません。これがオブジェクトを返さない理由であり、ミスにつながります。({})を追加することで修正できます。

// This works, but is easy for someone to break again
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

ただし、これは依然として分かりにくく、かっこを外すことで簡単に壊れてしまう可能性があります。

この間違いを避けるために、return文を明示的に記述してください。

// ✅ This works and is explicit
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

コンポーネントがレンダリングされるたびに、useMemo内の計算が再実行されます

依存関係配列を2番目の引数として指定したことを確認してください!

依存関係配列を忘れると、useMemoは計算を毎回再実行します。

function TodoList({ todos, tab }) {
// 🔴 Recalculates every time: no dependency array
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

これは、依存関係配列を2番目の引数として渡す修正版です。

function TodoList({ todos, tab }) {
// ✅ Does not recalculate unnecessarily
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

これが役に立たない場合は、少なくとも1つの依存関係が前のレンダリングとは異なることが問題です。依存関係を手動でコンソールに出力することで、この問題をデバッグできます。

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);

その後、コンソールから異なる再レンダリングの配列を右クリックし、両方について「グローバル変数として保存」を選択します。最初のものがtemp1として保存され、2番目がtemp2として保存されたと仮定すると、ブラウザコンソールを使用して、両方の配列の各依存関係が同じかどうかを確認できます。

Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...

メモ化を中断する依存関係が見つかった場合は、それを削除する方法を見つけるか、それもメモ化します。


ループ内の各リスト項目に対してuseMemoを呼び出す必要がありますが、許可されていません

Chart コンポーネントがmemoでラップされているとします。ReportListコンポーネントが再レンダリングされる際に、リスト内のすべてのChartの再レンダリングをスキップしたいと考えています。しかし、ループ内でuseMemoを呼び出すことはできません。

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useMemo in a loop like this:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

代わりに、各アイテムのコンポーネントを抽出し、個々のアイテムのデータをメモ化します。

function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}

function Report({ item }) {
// ✅ Call useMemo at the top level:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

あるいは、useMemoを削除し、代わりにReport自体をmemoでラップすることもできます。itemプロップが変化しない場合、Reportは再レンダリングをスキップするため、Chartも再レンダリングをスキップします。

function ReportList({ items }) {
// ...
}

const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});