React の useMemo フックの使い方を具体例をまじえて解説【 TypeScript 】

ショウヘイ

どうも、ノマドクリエイターのショウヘイ(@shohei_creator)です。

ふーちゃん

ブログアシスタントのふーちゃんです。

React は JavaScript を扱うライブラリなので、コンポーネントを構成するために、関数で計算をおこなっています。

純粋関数の計算であれば、引数が同じなら、必ず同じ返り値を得られます。この返り値をメモしておければ、同じ引数が渡された時にメモ化した返り値が使えるので、大幅に計算処理を削減できます。

この記事では、 React で関数の返り値をメモ化できる useMemo フックについて説明します。

目次

React の useMemo フックとは

React の useMemo フックは、純粋関数の返り値をメモ化(保持)できる機能です。

メモ化とは

何かしらの処理を実行した時に、実行によって得られた値を保持しておき、再利用する手法。あとで同じ処理が必要になった時に、保持しておいた値を使い回せるので、処理を高速化できる。

純粋関数の計算は、同じ引数を渡せば、必ず同じ返り値を得られます。そのため、『純粋関数に同じ引数が渡されるたびに、同じ計算を繰り返して、同じ返り値を得る』という工程は、とても非効率です。

もしも、『引数と返り値』のセットをメモしておければ、純粋関数に同じ引数が渡された時に、メモ化した返り値を使い回せるようになります。わざわざ計算する必要がありません。

React は SPA (シングル・ページ・アプリケーション)なので、頻繁に再レンダリングされます。もちろん、再びレンダリングのたびに、コンポーネントを構成する大量の関数を実行して、差分を検知しています。

useMemo フックを使えば、関数による計算処理を削減できるので、ブラウザのパフォーマンス向上が期待できます。

ショウヘイ

React は、とあるコンポーネントの state や props などに更新があった時に、『そのコンポーネントにぶら下がっている全ての子コンポーネント』も再レンダリングの対象にします。

マウントされる部分が少なくても、その背景では、膨大なレンダリングがおこなわれているわけです。

ふーちゃん

フック呼び出しのオーバーヘッドを考慮しつつ、 useMemo フックを使って、純粋関数をメモ化していきましょう。

複雑な計算が必要な純粋関数(高価な関数)ほど、 useMemo フックが真価を発揮してくれます。


React の useMemo フックの基本構文

React の useMemo フックを使う時は、 useMemo の第 1 引数として、純粋関数を設定します。つまり、純粋関数をコールバック関数として扱います。また、 useMemo フックの第 2 引数には、『更新を監視したい変数を要素として持つ依存配列』を設定します。

こちらは、 JavaScript で useMemo を書いた場合のコードです。

const memoizedValue = useMemo(() => computeExpensiveFunction(a, b), [a, b]);

こちらは、 TypeScript で useMemo を書いた場合のコードです。 useMemo の直後に、返り値を型宣言します。

const memoizedValue = useMemo<Type>(() => computeExpensiveFunction(a, b), [a, b]);
ショウヘイ

useMemo を使う時には、はじめに「 react 」から「 useMemo 」を import してください。

import React, { useMemo } from "react";

useMemo フックの依存配列が要素を持っている場合

React の useMemo の依存配列が要素として変数を持っている場合は、その変数は監視対象になります。変数に更新があった時には、あらためて純粋関数が実行されて、新しく得られた返り値がメモ化されます。

ショウヘイ

useMemo の挙動が分かりやすいように、 2 つの計算機を用意しました。

一方は、入力欄の数値を 2 倍にして表示します。もう一方は、入力欄の数値を 3 倍にして表示します。

入力欄の数字を変えると関数が実行されて、ページの倍数値を表示しつつ、コンソールにも出力します。

ふーちゃん

たとえば、 2 倍用の入力欄の数値を変えても、 3 倍用の関数が実行されていない( console.log が実行されない)ことに注目してください。

3 倍用の関数の依存配列の変数( inputTripel )が更新されていないので、メモ化が機能して、レンダリングされなかった証拠ですよ。

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

export const Counter: React.VFC = () => {
  const [inputDouble, setInputDouble] = useState<number>(5);
  const [inputTripel, setInputTripel] = useState<number>(5);

  const CalculateDouble = (num: number) => {
    let double = num * 2;
    console.log("double: " + double);
    return double;
  };

  const CalculateTriple = (num: number) => {
    let triple = num * 3;
    console.log("triple: " + triple);
    return triple;
  };

  // useMemoの依存配列の要素にdoubleNumを設定
  const doubleNum = useMemo<number>(() => CalculateDouble(inputDouble), [
    inputDouble
  ]);

  // useMemoの依存配列の要素にtripleNumを設定
  const tripleNum = useMemo<number>(() => CalculateTriple(inputTripel), [
    inputTripel
  ]);

  return (
    <>
      <div style={{ marginBottom: "40px" }}>
        <p>入力値の2倍: {doubleNum}</p>
        <input
          type="number"
          defaultValue="5"
          onChange={(e) => setInputDouble(Number(e.target.value))}
        />
      </div>
      <div>
        <p>入力値の3倍: {tripleNum}</p>
        <input
          type="number"
          defaultValue="5"
          onChange={(e) => setInputTripel(Number(e.target.value))}
        />
      </div>
    </>
  );
};

useMemo フックの依存配列が空になっている場合

React の useMemo の依存配列を空にした場合は、 useMemo 内の関数をはじめて実行した時のみ、メモ化されます。あとから関数の内部で使っている変数が更新されて、返り値も更新されるべき場合でも、初回実行時にメモ化した返り値が再使用されます。 

ショウヘイ

useMemo の依存配列を空にした場合です。

最初にページが表示された時点で、計算結果がメモ化されます。入力値を変更しても、常にメモ化した返り値が使われます。

ふーちゃん

どちらの入力欄の数値を変えても、ページに表示されている倍数値が変わりません。

また、コンソールログの出力も、はじめてページ表示された時の内容のみです。

メモ化が機能し続けて、再レンダリングされない証拠ですね。

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

export const Counter: React.VFC = () => {
  const [inputDouble, setInputDouble] = useState<number>(5);
  const [inputTripel, setInputTripel] = useState<number>(5);

  const CalculateDouble = (num: number) => {
    let double = num * 2;
    console.log("double: " + double);
    return double;
  };

  const CalculateTriple = (num: number) => {
    let triple = num * 3;
    console.log("triple: " + triple);
    return triple;
  };

  // useMemoの依存配列を空にする
  const doubleNum = useMemo<number>(() => CalculateDouble(inputDouble), []);

  // useMemoの依存配列を空にする
  const tripleNum = useMemo<number>(() => CalculateTriple(inputTripel), []);

  return (
    <>
      <div style={{ marginBottom: "40px" }}>
        <p>入力値の2倍: {doubleNum}</p>
        <input
          type="number"
          defaultValue="5"
          onChange={(e) => setInputDouble(Number(e.target.value))}
        />
      </div>
      <div>
        <p>入力値の3倍: {tripleNum}</p>
        <input
          type="number"
          defaultValue="5"
          onChange={(e) => setInputTripel(Number(e.target.value))}
        />
      </div>
    </>
  );
};

React.memo ・ useCallback ・ useMemo の違い 

React のメモ化に関する機能として、 React.memo ・ useCallback ・ useMemo の 3 つが挙げられます。それぞれの機能を簡潔にまとめると、以下のような違いがあります。


  • React.memo
    • コンポーネントそのものをメモ化できる。コンポーネントに渡される props を監視しており、 props が同じ場合には、メモ化したコンポーネントを使い回す。コンポーネントに対する不要なレンダリングを省略できる。 
  • useCallback フック
    • コールバック関数をメモ化できる。依存配列の要素として設定した変数を監視しており、変数が同じ場合には、メモ化した関数を使い回す。関数を再生成する工程を省略できる。基本的には、 React.memo と併用する。 
  • useMemo フック
    • 純粋関数の返り値をメモ化できる。依存配列の要素として設定した変数を監視しており、変数が同じ場合には、メモ化した返り値を使い回す。純粋関数に対する不要な計算を省略できる。

React.memo ・ useCallback ・ useMemo の違いついての参考記事

Qiita
React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする - Qiita はじめに React(v16.12.0)のReact.memo、useCallback、useMemoの基本的な使い方、使い所に関しての備忘録です。 「React でのパフォーマンス最適化の手段を知りたい...
この記事を知り合いに共有する
URLをコピーする
URLをコピーしました!
目次
閉じる