← ホームに戻る

React Hooks - useEffectとuseMemo

副作用処理とパフォーマンス最適化を学ぼう

useEffectとuseMemo

より高度な状態管理とパフォーマンス最適化を学びましょう。


useEffect - 副作用の処理

**副作用(Side Effect)**とは、コンポーネントの外の世界に影響を与える処理のことです。

副作用の例:

  • データの取得(API呼び出し)
  • DOM操作(document.title の変更など)
  • タイマーの設定
  • イベントリスナーの登録

useEffectの基本

書き方

import { useEffect } from 'react'; useEffect(() => { // ここに副作用の処理を書く }, [依存配列]);

依存配列とは?

依存配列に指定した値が変わった時だけ、useEffectが実行されます。

パターン1: 依存配列なし(毎回実行)

useEffect(() => { console.log('レンダリングのたびに実行される'); });

パターン2: 空の依存配列(1回だけ実行)

useEffect(() => { console.log('最初のレンダリング時だけ実行される'); }, []);

パターン3: 依存配列あり(特定の値が変わった時)

const [count, setCount] = useState(0); useEffect(() => { console.log('countが変わった時に実行される'); }, [count]);

useEffectの実践例

例1: ページタイトルの更新

import { useState, useEffect } from 'react'; function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `カウント: ${count}`; }, [count]); return ( <div> <p>カウント: {count}</p> <button onClick={() => setCount(count + 1)}>増やす</button> </div> ); }

ブラウザのタブのタイトルが「カウント: 0」→「カウント: 1」→...と変わります。

例2: データの取得

import { useState, useEffect } from 'react'; interface User { id: number; name: string; } function UserList() { const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { // APIからデータを取得 fetch('https://api.example.com/users') .then(response => response.json()) .then(data => { setUsers(data); setLoading(false); }); }, []); // 最初の1回だけ実行 if (loading) { return <p>読み込み中...</p>; } return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }

例3: タイマー

import { useState, useEffect } from 'react'; function Timer() { const [seconds, setSeconds] = useState(0); useEffect(() => { const interval = setInterval(() => { setSeconds(prev => prev + 1); }, 1000); // クリーンアップ関数(コンポーネントが削除される時に実行) return () => { clearInterval(interval); }; }, []); return <p>{seconds}秒経過</p>; }

クリーンアップ関数は、メモリリークを防ぐために重要です。


useMemo - 計算結果のメモ化

**メモ化(Memoization)**とは、計算結果をキャッシュして、同じ計算を繰り返さないようにすることです。

なぜuseMemoが必要?

重い計算がある場合、毎回計算し直すと遅くなります。

問題のある例(重い計算が毎回実行される)

import { useState } from 'react'; function ExpensiveCalculation() { const [count, setCount] = useState(0); const [other, setOther] = useState(0); // 重い計算(1から10000000までの合計) const sum = (() => { console.log('計算実行!'); let total = 0; for (let i = 1; i <= 10000000; i++) { total += i; } return total; })(); return ( <div> <p>合計: {sum}</p> <button onClick={() => setCount(count + 1)}> カウント: {count} </button> <button onClick={() => setOther(other + 1)}> 他のボタン: {other} </button> </div> ); }

「他のボタン」を押しても、毎回重い計算が実行されてしまいます。

useMemoで最適化

import { useState, useMemo } from 'react'; function ExpensiveCalculation() { const [count, setCount] = useState(0); const [other, setOther] = useState(0); // useMemoで計算結果をキャッシュ const sum = useMemo(() => { console.log('計算実行!'); let total = 0; for (let i = 1; i <= 10000000; i++) { total += i; } return total; }, []); // 依存配列が空なので、最初の1回だけ計算 return ( <div> <p>合計: {sum}</p> <button onClick={() => setCount(count + 1)}> カウント: {count} </button> <button onClick={() => setOther(other + 1)}> 他のボタン: {other} </button> </div> ); }

「他のボタン」を押しても、計算は実行されません!


useMemoの実践例

例1: フィルタリング結果のメモ化

import { useState, useMemo } from 'react'; interface Product { id: number; name: string; price: number; } function ProductList() { const [products] = useState<Product[]>([ { id: 1, name: "ノートPC", price: 120000 }, { id: 2, name: "マウス", price: 3000 }, { id: 3, name: "キーボード", price: 8000 }, ]); const [minPrice, setMinPrice] = useState(0); // フィルタリング結果をメモ化 const filteredProducts = useMemo(() => { console.log('フィルタリング実行'); return products.filter(p => p.price >= minPrice); }, [products, minPrice]); // minPriceが変わった時だけ再計算 return ( <div> <input type="number" value={minPrice} onChange={(e) => setMinPrice(Number(e.target.value))} placeholder="最低価格" /> <ul> {filteredProducts.map(product => ( <li key={product.id}> {product.name}: ¥{product.price} </li> ))} </ul> </div> ); }

例2: ソート結果のメモ化

import { useState, useMemo } from 'react'; function SortedList() { const [items] = useState([3, 1, 4, 1, 5, 9, 2, 6]); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); const sortedItems = useMemo(() => { console.log('ソート実行'); const sorted = [...items].sort((a, b) => a - b); return sortOrder === 'desc' ? sorted.reverse() : sorted; }, [items, sortOrder]); return ( <div> <button onClick={() => setSortOrder('asc')}>昇順</button> <button onClick={() => setSortOrder('desc')}>降順</button> <ul> {sortedItems.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); }

useEffectとuseMemoの違い

| | useEffect | useMemo | |---|---|---| | 用途 | 副作用の処理 | 計算結果のキャッシュ | | 実行タイミング | レンダリング後 | レンダリング中 | | 戻り値 | なし(またはクリーンアップ関数) | 計算結果 | | 例 | API呼び出し、DOM操作 | 重い計算、フィルタリング |


いつuseMemoを使うべき?

✅ 使うべき場合

  • 計算が重い(配列のソート、大量データのフィルタリングなど)
  • 計算結果を子コンポーネントに渡す場合

❌ 使わなくていい場合

  • 計算が軽い(単純な足し算など)
  • 依存配列の値が頻繁に変わる場合

原則: 最初からuseMemoを使わず、パフォーマンスの問題が出たら使う。


まとめ

useEffect

  • 副作用(外部への影響)を扱う
  • 依存配列で実行タイミングを制御
  • クリーンアップ関数でリソースを解放
  • 用途:API呼び出し、DOM操作、タイマー

useMemo

  • 計算結果をキャッシュ
  • 依存配列の値が変わった時だけ再計算
  • パフォーマンス最適化に使う
  • 用途:重い計算、ソート、フィルタリング

次は、TypeScriptとReactを組み合わせた実践的な型定義を学びましょう!