Skip to content

メモ

Published: at 16:32

reactテンプレートファイルに貼り付けて動く
useState, useEffect, useRef, useMemo, useCallback, useReducer, useContext, createContext
の学習用

Code

import React, { useState, useEffect, useRef, useMemo, useCallback, useReducer, useContext, createContext } from 'react';

interface User{
    name:string,
    email:string
}
// 1. useContext用のコンテキスト
const ThemeContext = createContext<{ isDark: boolean; toggleTheme: () => void }>({
  isDark: false,
  toggleTheme: () => {},
});

// 2. useReducer用のreducer
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
};

// useEffectの例 - データフェッチとクリーンアップ
const UseEffectExample = () => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // マウント時の処理
    console.log('コンポーネントがマウントされました');

    // 模擬的なAPIコール
    const fetchUser = async () => {
      setLoading(true);
      // 実際のAPIの代わりにsetTimeoutでシミュレート
      setTimeout(() => {
        setUser({ name: '田中太郎', email: 'tanaka@example.com' });
        setLoading(false);
      }, 1000);
    };

    fetchUser();

    // クリーンアップ関数
    return () => {
      console.log('useEffect クリーンアップ実行');
    };
  }, []); // 空の依存配列で初回のみ実行

  if (loading) return <div className="p-4 text-blue-600">ユーザー情報を読み込み中...</div>;

  return (
    <div className="p-4 bg-blue-50 rounded-lg">
      <h3 className="font-bold text-lg mb-2">useEffect例 - データフェッチ</h3>
      <p><strong>名前:</strong> {user?.name}</p>
      <p><strong>メール:</strong> {user?.email}</p>
    </div>
  );
};

// useRefの例 - DOM参照とフォーカス管理
const UseRefExample = () => {
  const inputRef = useRef<HTMLInputElement | null>(null);
  const [inputValue, setInputValue] = useState('');

  const focusInput = () => {
    inputRef.current?.focus();
  };

  const clearAndFocus = () => {
    setInputValue('');
    inputRef.current?.focus();
  };

  return (
    <div className="p-4 bg-green-50 rounded-lg">
      <h3 className="font-bold text-lg mb-2">useRef例 - DOM操作</h3>
      <div className="space-y-2">
        <input
          ref={inputRef}
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="ここに入力してください"
          className="w-full p-2 border rounded"
        />
        <div className="space-x-2">
          <button onClick={focusInput} className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600">
            フォーカス
          </button>
          <button onClick={clearAndFocus} className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
            クリア&フォーカス
          </button>
        </div>
        <p className="text-sm text-gray-600">入力値: {inputValue}</p>
      </div>
    </div>
  );
};

// useMemoの例 - 重い計算のメモ化
const UseMemoExample = () => {
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
  const [multiplier, setMultiplier] = useState(1);

  // 重い計算をシミュレート(実際はもっと複雑な処理)
  const expensiveCalculation = useMemo(() => {
    console.log('useMemo: 重い計算を実行中...');
    return numbers.reduce((sum, num) => sum + (num * multiplier), 0);
  }, [numbers, multiplier]); // numbersまたはmultiplierが変わった時のみ再計算

  const addNumber = () => {
    setNumbers(prev => [...prev, prev.length + 1]);
  };

  return (
    <div className="p-4 bg-yellow-50 rounded-lg">
      <h3 className="font-bold text-lg mb-2">useMemo例 - 重い計算のメモ化</h3>
      <div className="space-y-2">
        <p>数値: [{numbers.join(', ')}]</p>
        <p>乗数: {multiplier}</p>
        <p><strong>計算結果: {expensiveCalculation}</strong></p>
        <div className="space-x-2">
          <button onClick={addNumber} className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600">
            数値追加
          </button>
          <button onClick={() => setMultiplier(prev => prev + 1)} className="px-4 py-2 bg-orange-500 text-white rounded hover:bg-orange-600">
            乗数+1
          </button>
        </div>
        <p className="text-xs text-gray-500">※コンソールで計算実行のログを確認してください</p>
      </div>
    </div>
  );
};

// useCallbackの例 - 関数のメモ化
const UseCallbackExample = () => {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState(['アイテム1', 'アイテム2']);

  // useCallbackで関数をメモ化
  const addItem = useCallback(() => {
    setItems(prev => [...prev, `アイテム${prev.length + 1}`]);
  }, []); // 依存配列が空なので、この関数は初回作成時のみ

  const resetItems = useCallback(() => {
    setItems(['アイテム1', 'アイテム2']);
  }, []);

  return (
    <div className="p-4 bg-purple-50 rounded-lg">
      <h3 className="font-bold text-lg mb-2">useCallback例 - 関数のメモ化</h3>
      <div className="space-y-2">
        <p>カウント: {count}</p>
        <p>アイテム数: {items.length}</p>
        <ul className="list-disc list-inside">
          {items.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
        <div className="space-x-2">
          <button onClick={() => setCount(prev => prev + 1)} className="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600">
            カウント+1
          </button>
          <button onClick={addItem} className="px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600">
            アイテム追加
          </button>
          <button onClick={resetItems} className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
            リセット
          </button>
        </div>
      </div>
    </div>
  );
};

// useReducerの例 - 複雑な状態管理
const UseReducerExample = () => {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div className="p-4 bg-red-50 rounded-lg">
      <h3 className="font-bold text-lg mb-2">useReducer例 - 複雑な状態管理</h3>
      <div className="space-y-2">
        <p className="text-2xl font-bold">カウント: {state.count}</p>
        <div className="space-x-2">
          <button
            onClick={() => dispatch({ type: 'increment' })}
            className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
          >
            +1
          </button>
          <button
            onClick={() => dispatch({ type: 'decrement' })}
            className="px-4 py-2 bg-pink-500 text-white rounded hover:bg-pink-600"
          >
            -1
          </button>
          <button
            onClick={() => dispatch({ type: 'reset' })}
            className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
          >
            リセット
          </button>
        </div>
      </div>
    </div>
  );
};

// useContextの例 - テーマプロバイダー
const UseContextExample = () => {
  const theme = useContext(ThemeContext);

  return (
    <div className={`p-4 rounded-lg ${theme?.isDark ? 'bg-gray-800 text-white' : 'bg-gray-100 text-black'}`}>
      <h3 className="font-bold text-lg mb-2">useContext例 - テーマ管理</h3>
      <p>現在のテーマ: {theme?.isDark ? 'ダーク' : 'ライト'}</p>
      <button
        onClick={theme?.toggleTheme}
        className={`mt-2 px-4 py-2 rounded ${
          theme?.isDark
            ? 'bg-white text-black hover:bg-gray-200'
            : 'bg-black text-white hover:bg-gray-800'
        }`}
      >
        テーマ切替
      </button>
    </div>
  );
};

// メインコンポーネント
const HooksExamples = () => {
  const [isDark, setIsDark] = useState(false);

  const themeValue = {
    isDark,
    toggleTheme: () => setIsDark(prev => !prev)
  };

  return (
    <ThemeContext.Provider value={themeValue}>
      <div className="max-w-4xl mx-auto p-6 space-y-6">
        <h1 className="text-3xl font-bold text-center mb-8">React Hooks 学習例集</h1>

        <div className="grid gap-6">
          <UseEffectExample />
          <UseRefExample />
          <UseMemoExample />
          <UseCallbackExample />
          <UseReducerExample />
          <UseContextExample />
        </div>

        <div className="mt-8 p-4 bg-gray-50 rounded-lg">
          <h3 className="font-bold text-lg mb-2">学習ポイント</h3>
          <ul className="space-y-1 text-sm">
            <li><strong>useState:</strong> 基本的な状態管理(この例では複数箇所で使用中)</li>
            <li><strong>useEffect:</strong> 副作用の処理、データフェッチ、クリーンアップ</li>
            <li><strong>useRef:</strong> DOM要素への参照、値の永続化</li>
            <li><strong>useMemo:</strong> 重い計算結果のメモ化、パフォーマンス最適化</li>
            <li><strong>useCallback:</strong> 関数のメモ化、子コンポーネントの不要な再レンダリング防止</li>
            <li><strong>useReducer:</strong> 複雑な状態管理、Reduxライクなパターン</li>
            <li><strong>useContext:</strong> コンポーネント間でのデータ共有</li>
          </ul>

          <h4 className="font-bold mt-4 mb-2">この例でuseStateが使われている箇所:</h4>
          <ul className="space-y-1 text-xs text-gray-600">
            <li>• UseEffectExample: user, loading状態の管理</li>
            <li>• UseRefExample: inputValue状態の管理</li>
            <li>• UseMemoExample: numbers, multiplier状態の管理</li>
            <li>• UseCallbackExample: count, items状態の管理</li>
            <li>• HooksExamples: isDarkテーマ状態の管理</li>
          </ul>
        </div>
      </div>
    </ThemeContext.Provider>
  );
};

export default HooksExamples;