0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[React][Hooks] `useMemo` の使い方

Posted at

概要

  • Reactアプリケーションで、高コストな計算結果のメモ化レンダー間での計算結果保持を実現できる useMemo フックを紹介
  • レンダリングごとに再計算される処理の最適化に有効
  • 文字列加工、配列やオブジェクトのフィルタリング・ソート計算、複雑な数値計算などで頻出

useMemo によって 「不要な再計算を避けてパフォーマンスを向上させる」 UIを作成できる

実施条件

  • React + TypeScript プロジェクトが構築済みであること
  • React Hooksの基本(useState, useEffect など)を理解していること

環境

ツール バージョン 目的
Node.js 22.5.1 Reactアプリ実行環境
React 19.1.0 UI構築
TypeScript 4.9 型定義による安全な開発

useMemo<T>(計算関数, 依存配列) の役割

  • useMemo のジェネリクス <T> は、返り値がどんな型かをTypeScriptに伝える
  • 依存配列が変化したときのみ計算を実行し、変化がない場合は前回の値を再利用

useMemo の基本構造

  1. importセクション
  2. 型定義セクション
  3. 関数定義セクション
    3.1 内部状態管理セクション
    3.2 イベントハンドラー処理セクション
    3.3 副作用処理セクション
    3.4 返り値構築・ロジックセクション

基本構文

// 1. importセクション
import React, { useState, useMemo } from 'react';

// 2. 型定義セクション
// 今回は不要(返り値型は自動推論される)

// 3. 関数定義セクション
const ExpensiveCalculation = () => {
  // 3.1 内部状態管理セクション
  const [count, setCount] = useState(0);

  // 3.2 イベントハンドラー処理セクション
  const handleIncrement = () => setCount((c) => c + 1);

  // 3.3 副作用処理セクション
  // 今回は副作用なし(計算は useMemo により最適化)

  // 3.4 返り値構築・ロジックセクション
  const double = useMemo(() => {
    console.log('計算実行');
    return count * 2;
  }, [count]);

  return (
    <div style={{ padding: '1rem' }}>
      <p>現在の値: {count}</p>
      <p>計算結果(2倍): {double}</p>
      <button onClick={handleIncrement}>+1</button>
    </div>
  );
};

export default ExpensiveCalculation;

活用例

1. 文字列の長さ制限(タイトル省略)

// 1. importセクション
import React, { useMemo } from 'react';

// 2. 型定義セクション
interface Props {
  title: string;
  maxTitleLength: number;
}

// 3. 関数定義セクション
const TruncatedTitle: React.FC<Props> = ({ title, maxTitleLength }) => {
  // 3.1 内部状態管理セクション
  // 今回は特になし(props利用)

  // 3.2 イベントハンドラー処理セクション
  // 今回は特になし

  // 3.3 副作用処理セクション
  // 今回は特になし

  // 3.4 返り値構築・ロジックセクション
  const truncatedTitle = useMemo(() => {
    if (title.length <= maxTitleLength) {
      return title;
    }

    const words = title.split(' ');
    let result = '';

    for (const word of words) {
      const testResult = result ? `${result} ${word}` : word;
      if (testResult.length <= maxTitleLength - 1) {
        result = testResult;
      } else {
        break;
      }
    }

    if (!result) {
      result = title.substring(0, maxTitleLength - 1);
    }

    return result + '';
  }, [title, maxTitleLength]);

  return <p>{truncatedTitle}</p>;
};

export default TruncatedTitle;

2. 配列のフィルタリング(検索ワード)

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

const FilterList = () => {
  // 3.1 内部状態管理セクション
  const [search, setSearch] = useState('');
  const items = ['apple', 'banana', 'orange', 'grape', 'melon'];

  // 3.2 イベントハンドラー処理セクション
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.target.value);

  // 3.3 副作用処理セクション
  // 今回はなし

  // 3.4 返り値構築・ロジックセクション
  const filteredItems = useMemo(() => {
    console.log('フィルタリング実行');
    return items.filter((item) => item.includes(search));
  }, [search]);

  return (
    <div style={{ padding: '1rem' }}>
      <input type="text" value={search} onChange={handleChange} placeholder="検索ワード" />
      <ul>
        {filteredItems.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default FilterList;

3. 高コスト計算:配列の合計値

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

const SumCalculator = () => {
  // 3.1 内部状態管理セクション
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);

  // 3.2 イベントハンドラー処理セクション
  const handleAddNumber = () =>
    setNumbers([...numbers, numbers.length + 1]);

  // 3.3 副作用処理セクション
  // 今回はなし

  // 3.4 返り値構築・ロジックセクション
  const sum = useMemo(() => {
    console.log('合計計算実行');
    return numbers.reduce((acc, n) => acc + n, 0);
  }, [numbers]);

  return (
    <div style={{ padding: '1rem' }}>
      <p>配列: {numbers.join(', ')}</p>
      <p>合計: {sum}</p>
      <button onClick={handleAddNumber}>数字追加</button>
    </div>
  );
};

export default SumCalculator;

useMemo の用途まとめ

ユースケース 解説
高コスト計算の最適化 レンダリングごとに再計算される処理を制御
配列やオブジェクト操作 フィルタリング、ソート、集計などの計算結果保持
文字列加工 長さ制限、整形、変換処理を効率化
レンダリング最適化 再レンダリング時に無駄な計算を避けてパフォーマンス向上

useRef, useMemo, useCallback の違い

フック 主な役割 値の更新タイミング レンダーへの影響 ユースケース
useRef レンダーに依存しない値の保持 .current に代入したとき しない(再レンダーをトリガーしない) - DOM要素へのアクセス(例: inputRef.current.focus()
- タイマーID管理
- 前回値の保持
- 外部ライブラリ連携
useMemo 値のメモ化(計算結果キャッシュ) 依存配列の値が変わったとき する(依存値が変われば計算してレンダーに反映) - 計算コストの高い処理の最適化
- 文字数制限処理
- リストのフィルタリングなどUIに使う計算
useCallback 関数のメモ化 依存配列の値が変わったと しない(関数参照が安定するだけ) - 子コンポーネントにコールバックを渡す
- 再レンダー最適化

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?