見出し画像

ウェブシステムのパフォーマンス向上に役立つ useMemo と useCallback - 違いを理解し正しく使いこなそう

今回は、ウェブシステムのパフォーマンス最適化に役立つ React の重要な2つの機能、useMemo と useCallback について見ていきます。これらはパフォーマンス向上に効果的ですが、使い方が混乱しやすいこともあります。ここで、それぞれの仕組みを理解し、使いこなせるようにしましょう。まずは「参照の等価性」という概念から説明します。

参照の等価性 - なぜ重要なのか

Reactにおいて、参照の等価性とは、2つの変数がメモリ上の同じオブジェクトまたは関数を指していることを指します。Reactはこの原則に基づいて、コンポーネントを再レンダリングすべきかどうかを判断します。
Reactが再レンダリングを管理する方法:

  • プリミティブ型:(例:文字列、数値)Reactはこれらをで比較します。値が変更されていない場合、Reactはコンポーネントを再レンダリングしません。

  • 参照型:(例:オブジェクト、配列、関数)Reactはこれらを参照で比較します。2つのオブジェクトが同じ値を持っていても、参照が異なれば再レンダリングがトリガーされます。ここでuseMemoとuseCallbackが役立ちます。

useMemo:重たいな計算に活用

useMemoを使用すると、重たい計算の結果をキャッシュでき、依存関係が変更された場合にのみ結果を再計算するようReactに指示できます。これにより、毎回のレンダリング時に結果を再計算することを防ぎます。

const memoizedValue = useMemo(() => {
  // ここに高コストな計算を記述
  return calculatedValue;
}, [dependencies]);

useMemoをいつ使用すべきか?

  • 重たい計算の場合:コンポーネントが複雑な計算(例:大きなリストのソート、階乗の計算)を実行する場合、useMemoは優れた選択肢です。必要な時にのみ計算が実行されることを保証します。

import React, { useState, useMemo } from 'react';

const calculateFactorial = (number) => {
  if (number <= 0) return 1;
  return number * calculateFactorial(number - 1);
};

const FactorialComponent = () => {
  const [number, setNumber] = useState(5);
  const factorial = useMemo(() => calculateFactorial(number), [number]);

  return (
    <div>
      <p>{number}の階乗は:{factorial}です</p>
      <button onClick={() => setNumber(number + 1)}>増加</button>
      <button onClick={() => setNumber(number - 1)}>減少</button>
    </div>
  );
};

この場合、useMemoは数値が変更された時にのみ階乗の計算が実行されるようにし、パフォーマンスを向上させます。

  • オブジェクト/配列のメモ化:コンポーネント内でオブジェクトや配列を作成する場合、それらの参照は毎回のレンダリングで変更され、不必要な再レンダリングを引き起こす可能性があります。useMemoは、オブジェクトや配列内の値が変更されない限り、同じ参照を維持することでこれを防ぎます。

const filter = useMemo(() => ({
  search: query,
  sortBy: 'date',
  order: 'asc'
}), [query]);

useMemoを使用しない場合、Reactは毎回のレンダリングで新しいfilterオブジェクトを作成し、queryの値が変更されていなくても不必要な再レンダリングをトリガーします。

useCallback:関数をメモ化

useCallbackはuseMemoと同様に動作しますが、値をメモ化する代わりに関数をメモ化します。これは特に子コンポーネントに関数を渡す際に有用です。関数の参照が変更されると、Reactは不必要に子コンポーネントを再レンダリングします。useCallbackは、依存関係が変更されない限り、関数の参照が同じままであることを保証します。

const memoizedCallback = useCallback(() => {
  // ここにコールバック関数のロジックを記述
}, [dependencies]);

useCallbackをいつ使用すべきか?

  • 子コンポーネントに関数を渡す場合:関数をpropsとして子コンポーネントに渡す場合、関数の参照が変更されると、Reactはその子の再レンダリングをトリガーします。useCallbackは関数をメモ化し、その参照を安定させることでこれを防ぎます。

import React, { useState, useCallback } from 'react';

const ChildComponent = React.memo(({ onClick }) => {
  console.log('子コンポーネントが再レンダリングされました');
  return <button onClick={onClick}>クリックしてください</button>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('ボタンがクリックされました');
  }, []);

  return (
    <div>
      <p>カウント:{count}</p>
      <button onClick={() => setCount(count + 1)}>増加</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

この例では、handleClickはuseCallbackを使用してメモ化され、親コンポーネントが更新された際に子コンポーネントが不必要に再レンダリングされることを防いでいます。
これらのフックを使用する際に注意すべき点

  1. メモリのオーバーヘッド:useMemoとuseCallbackは、Reactが以前の値や関数の参照を保存する必要があるため、わずかなメモリのオーバーヘッドを導入します。パフォーマンスに敏感な領域でのみ必要に応じて使用してください。

  2. 過剰使用:これらのフックを過度に使用することは避けてください。大規模なアプリケーションや、頻繁な再レンダリングがパフォーマンスを低下させるシナリオで最も有益です。

ベストプラクティス:最適化前にプロファイリングを実施する

useMemo や useCallback で処理を最適化する前に、処理をプロファイリングし、実際のパフォーマンスのボトルネックを特定することが重要です。早期の最適化は不必要な複雑さにつながる可能性があります。
Reactでのプロファイリング方法:

  1. React Developer Tools:Profiler機能を使用して、コンポーネントのレンダリングにかかる時間を測定し、不必要な再レンダリングを特定できます。

  2. ブラウザの開発者ツール:Performanceタブを使用して、メモリ使用量、ネットワークリクエスト、JavaScriptの実行時間を監視できます。

  3. コンソールログ:console.time()とconsole.timeEnd()を使用して、特定の操作の実行にかかる時間を測定できます。

まずプロファイリングを行うことで、最適化が必要な特定のコンポーネントを特定し、useMemoやuseCallbackを賢明に適用できます。
これでuseMemoとuseCallbackの違いを学びました。これらのフックを使用して、Reactアプリを賢明かつ効率的に最適化し始めることができます!
参考文献:

  1. FreeCodeCamp:useMemoとuseCallbackの違い

  2. Reactのパフォーマンス最適化:useMemo vs useCallback

読んでいただきありがとうございます。参考になりましたら幸いです。
気になることや質問がありましたら、ぜひコメントください。

この記事が役立つと感じた場合は、スキを押してもらえると嬉しいです!


この記事は、2024年9月に弊社のフロントエンドエンジニアである Suren Sedai が執筆した内容を日本語に翻訳したものです。
英語版はこちらからご覧いただけます。
https://articles.wesionary.team/usememo-vs-usecallback-their-differences-and-using-them-wisely-6abb0fa6be14


採用情報

私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!


開発パートナーをお探しの企業様へ

弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。