📝

【React】useMemoをなんとなくで使っていたので、ざっくりまとめてみました

2024/06/24に公開

useMemoとは

概要

  • ReactHooksのひとつ
  • 関数の計算結果を保持するためのフック
  • 何回行っても結果が同じ値を保存して再取得します
  • パフォーマンス向上のために使用されます
const cachedValue = useMemo(calculateValue, dependencies)

引数

  1. calculateValue :キャッシュしたい値を計算する関数。純関数[1]である必要があります。
  2. dependencies :calculateValue 内で参照されるすべてのリアクティブ値の配列。これが変わると再計算されます。
const result = useMemo(() => num * 2, [num]);

この場合 num が更新された際に、再計算される

返り値

初回レンダー時calculateValue の結果を返します。
次回以降のレンダー時:依存配列が変わらなければキャッシュされた値を返し、変われば再計算してその結果を返します。

注意点

  • フックなので、コンポーネントのトップレベル[2]でのみ使用可能。
  • ループや条件分岐の中では使用不可。
  • 特別な理由がない限り、キャッシュは破棄されませんが、将来的に変更される可能性があります。

使用場面

①高コストな再計算を避ける

useMemo は、高コストな再計算を避けるために、計算結果をキャッシュします。

通常、Reactでは再レンダーが発生するたびにコンポーネント関数全体が再実行されます。以下の例では、filterTodos 関数が毎回再実行されます。

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

しかし、useMemo を使用することで、todostab が変わらない限り、以前の計算結果を再利用できます。

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

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

useMemo は、子コンポーネントの再レンダリングのパフォーマンスを最適化する際にも役に立つことがあります

例①

以下の例では、TodoList コンポーネントが List コンポーネントに visibleTodos を渡しています。

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

theme が変わると、List コンポーネントも再レンダリングされ、アプリが一瞬フリーズすることがあります。これは、親コンポーネントが再レンダリングされると、子コンポーネントも再帰的に再レンダリングされるためです。

解決策として、List コンポーネントを memo でラップし、props が変わらない限り再レンダリングをスキップします。

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

例②

const TodoList = ({ todos, tab, theme }) => {
  const visibleTodos = filterTodos(todos, tab);
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
};

useMemo を使わずに visibleTodos を計算すると、filterTodos が毎回異なる配列を生成し、List の再レンダリングをスキップできません。

そこで、useMemo を使うことで、依存配列(todostab )が変わらない限り、計算結果をキャッシュし、同じ visibleTodos を渡すことができます。

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

③他フックに渡す依存値をメモ化

他のフックに渡す依存値をメモ化する方法

コンポーネント内で直接作成されたオブジェクトを依存値として使用すると、再レンダー時に毎回新しいオブジェクトが生成され、メモ化の効果が失われます。

以下の例では、searchOptions オブジェクトが毎回新しく生成されるため、useMemo の依存値が毎回変わり、再計算が発生します。

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

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // 🚩 注意: コンポーネント本体で作成されたオブジェクトに依存
  // ...
};

解決策①: searchOptions をメモ化

searchOptions オブジェクト自体を useMemo でメモ化します。

const Dropdown = ({ allItems, text }) => {
  const searchOptions = useMemo(() => {
    return { matchMode: 'whole-word', text };
  }, [text]); // ✅ text が変わらない限り変化しない

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ allItems または searchOptions が変わったときのみ再計算
  // ...
};

解決策②: useMemo の計算関数内にオブジェクトを移動

さらに良い方法は、オブジェクトの宣言を useMemo の計算関数内に移動することです。

const Dropdown = ({ allItems, text }) => {
  const visibleItems = useMemo(() => {
    const searchOptions = { matchMode: 'whole-word', text };
    return searchItems(allItems, searchOptions);
  }, [allItems, text]); // ✅ allItems または text が変わったときのみ再計算
  // ...
};

④関数のメモ化

関数をメモ化する方法

memo でラップされたコンポーネントに関数を props として渡す場合、毎回新しい関数が生成されるとメモ化の効果が失われます。

以下の例では、handleSubmit 関数が毎回新しく生成されるため、Form コンポーネントの再レンダーがスキップされません。

const ProductPage = ({ productId, referrer }) => {
  const handleSubmit = (orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails
    });
  };

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

関数を useMemo でメモ化します。

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

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

参考資料

▼React公式
https://ja.react.dev/reference/react/useMemo#skipping-re-rendering-of-components

脚注
  1. 純関数とは、以下の2つの条件を満たす関数のことを指します。副作用がないこと: 関数の実行が外部の状態に影響を与えないこと。例えば、関数内でグローバル変数を変更したり、ファイルやデータベースに書き込んだりしないことです。同じ入力に対して常に同じ出力を返すこと: 関数に同じ引数を渡した場合、常に同じ結果を返すことが保証されること。 ↩︎

  2. トップレベルのコンポーネントは、アプリケーションの最上位に位置するコンポーネントのことを指します。 ↩︎

Discussion