ReactとReduxの基本を学ぶ:サンプルプロジェクトで簡単スタート

ReactとReduxを活用したモダンなフロントエンド開発は、効率的な状態管理と再利用可能なコンポーネント設計が特徴です。しかし、初めて学ぶ方にはその概念や実装手順が難しく感じられるかもしれません。本記事では、ReactとReduxを学ぶための最適な方法として、シンプルなサンプルプロジェクトを題材に基本から応用までを順を追って解説します。このガイドを通じて、ReactとReduxの基礎をしっかりと習得し、実践的な開発スキルを身につけましょう。

目次

ReactとReduxの概要


Reactは、ユーザーインターフェースを構築するためのライブラリであり、コンポーネントベースの設計により、再利用可能なコードを簡単に作成できます。一方、Reduxはアプリケーションの状態管理を行うためのライブラリで、大規模なアプリケーションでも状態を一元管理できる利点があります。

Reactの特徴


Reactは、以下の特徴を持っています:

  • 仮想DOMを利用した高速なUI更新
  • 状態(state)とプロパティ(props)を通じた明確なデータフロー
  • 再利用可能なコンポーネントでの開発効率向上

Reduxの役割


Reduxは、以下の仕組みでアプリケーション全体の状態を管理します:

  • アプリケーションの状態をストアに一元化
  • ユーザー操作をアクションとして定義
  • 状態の変更をリデューサーで制御

ReactとReduxを併用する理由


Reactだけでも状態管理は可能ですが、アプリケーションが大規模化するとコンポーネント間のデータ共有が複雑になります。Reduxを組み合わせることで以下のメリットを得られます:

  • 状態管理のロジックをコンポーネントから分離
  • アプリケーション全体での状態共有が容易
  • 状態変更の流れを予測可能に

ReactとReduxは、それぞれの得意分野を補完し合うことで、効率的でスケーラブルなアプリケーション開発を可能にします。

Reduxの基本概念


Reduxは、アプリケーションの状態を一元管理するためのライブラリです。その動作は明確でシンプルな設計に基づいており、主に以下の3つの要素で構成されています。

1. アクション (Action)


アクションは、アプリケーションで何かが発生したことを表すプレーンなJavaScriptオブジェクトです。アクションオブジェクトには必ずtypeプロパティが含まれ、どのような処理を行うべきかを示します。

const incrementAction = {
  type: 'INCREMENT',
  payload: { amount: 1 }
};

アクションの役割

  • 状態の変更をトリガーする
  • 状態変更に必要なデータをペイロードとして渡す

2. リデューサー (Reducer)


リデューサーは、現在の状態とアクションを受け取り、次の状態を返す純粋関数です。状態の変更ロジックはすべてリデューサーに記述します。

function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + action.payload.amount };
    case 'DECREMENT':
      return { count: state.count - action.payload.amount };
    default:
      return state;
  }
}

リデューサーの役割

  • 状態の変化を一箇所にまとめる
  • 状態管理のロジックを明確化する

3. ストア (Store)


ストアは、状態を保持し、アクションをリデューサーに渡す役割を担います。アプリケーション全体の状態を一元管理し、状態変更時にはリスナーを通知します。

import { createStore } from 'redux';

const store = createStore(counterReducer);

console.log(store.getState()); // 初期状態
store.dispatch(incrementAction); // アクションの送信
console.log(store.getState()); // 更新された状態

ストアの役割

  • 状態の格納と管理
  • アクションをディスパッチして状態を更新
  • 状態変更時の通知

Reduxのデータフロー

  1. アクションをディスパッチ
    ユーザーの操作やイベントに応じてアクションを送信します。
  2. リデューサーで状態を更新
    リデューサーが現在の状態とアクションを元に新しい状態を計算します。
  3. 新しい状態をストアに保存
    ストアが新しい状態を保持し、UIに変更を通知します。

Reduxのこれらの概念を理解することで、状態管理の全体像を把握しやすくなります。

サンプルプロジェクトのセットアップ手順


Reduxを学ぶために、簡単なサンプルプロジェクトを構築します。このセクションでは、プロジェクトのセットアップ手順を詳しく解説します。

1. 必要な環境の準備


以下のツールをインストールしてください:

  • Node.js(公式サイトから最新バージョンをダウンロード)
  • npmまたはyarn(Node.jsに同梱)

インストールの確認


ターミナルで以下のコマンドを実行し、インストール状況を確認します:

node -v
npm -v

2. プロジェクトの作成


プロジェクトを作成するために、npxコマンドを使用します:

npx create-react-app redux-sample --template redux

このコマンドにより、Reduxのテンプレートを使用したReactプロジェクトが作成されます。

ディレクトリ構造の確認


プロジェクトディレクトリに移動して、内容を確認します:

cd redux-sample
ls

3. 必要なパッケージのインストール


プロジェクトに必要な依存パッケージをインストールします。以下は一般的な追加パッケージです:

npm install @reduxjs/toolkit react-redux

パッケージの役割

  • @reduxjs/toolkit: Reduxの推奨ツールキットで、コードの簡略化が可能
  • react-redux: ReactとReduxを連携するためのライブラリ

4. 初期設定ファイルの作成


Reduxのストアを作成し、プロジェクト全体に適用する設定を行います。

  1. src/store.jsを作成:
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {}, // 後でリデューサーを追加
});

export default store;
  1. src/index.jsを編集:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

5. サンプルアプリの確認


プロジェクトを起動して、セットアップが正しく行われたか確認します:

npm start

ブラウザでhttp://localhost:3000にアクセスすると、デフォルトのReactアプリが表示されます。

次のステップ


ここまででプロジェクトの準備が整いました。次は、Reduxによる状態管理を実装していきます。

Reduxによる状態管理の実装方法


ここでは、サンプルプロジェクトを使って、Reduxを用いた状態管理を実装する方法を解説します。アクション、リデューサー、ストアを順に設定し、Reactコンポーネントで状態を操作する基本を学びます。

1. 状態の定義


まず、Reduxで管理する状態を決定します。ここでは、カウンターアプリを例にします。

2. アクションの作成


アクションを定義するために、Redux ToolkitのcreateSliceを使います。

src/features/counter/counterSlice.js:

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

3. リデューサーをストアに追加


ストアにリデューサーを登録します。

src/store.js:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

4. Reactコンポーネントで状態を利用


Reduxの状態をReactコンポーネントで操作します。React-ReduxのuseSelectoruseDispatchを使用します。

src/features/counter/Counter.js:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</button>
    </div>
  );
}

export default Counter;

5. コンポーネントをアプリに組み込む


作成したコンポーネントをアプリケーションに組み込みます。

src/App.js:

import React from 'react';
import Counter from './features/counter/Counter';

function App() {
  return (
    <div>
      <h1>Redux Counter App</h1>
      <Counter />
    </div>
  );
}

export default App;

6. 実行して確認


プロジェクトを再起動して、カウンターアプリが動作することを確認します:

npm start

まとめ


このセクションでは、Reduxによる基本的な状態管理の流れを実装しました。次に、ReactコンポーネントとReduxの連携についてさらに詳しく学びます。

ReactコンポーネントとReduxの接続


ReactとReduxを効果的に連携させることで、状態管理を簡潔に扱えるようになります。このセクションでは、connect関数とReact Hooksを使用した接続方法を解説します。

1. `connect`関数を使用した接続


従来の方法として、connect関数を使用してReduxのストアにコンポーネントを接続する方法を紹介します。

src/features/counter/CounterWithConnect.js:

import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

function CounterWithConnect({ count, increment, decrement, incrementByAmount }) {
  return (
    <div>
      <h1>Counter (Connect): {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={() => incrementByAmount(5)}>Increment by 5</button>
    </div>
  );
}

const mapStateToProps = (state) => ({
  count: state.counter.value,
});

const mapDispatchToProps = {
  increment,
  decrement,
  incrementByAmount,
};

export default connect(mapStateToProps, mapDispatchToProps)(CounterWithConnect);

手順のポイント

  • mapStateToProps: ストアの状態をコンポーネントのプロパティにマッピングします。
  • mapDispatchToProps: アクションをプロパティとしてコンポーネントに渡します。

2. React Hooksを使用した接続


現在の推奨方法として、React Hooksを使う方法があります。Redux ToolkitとHooksを併用することで、コードが簡潔になります。

src/features/counter/CounterWithHooks.js:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

function CounterWithHooks() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter (Hooks): {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</button>
    </div>
  );
}

export default CounterWithHooks;

手順のポイント

  • useSelector: ストアの状態を取得します。
  • useDispatch: アクションをディスパッチする関数を取得します。

3. どちらを選ぶべきか?


現在のRedux公式ドキュメントでは、React Hooksを使用する方法が推奨されています。以下の理由から、Hooksを使用する方が適しています:

  • コードが短く読みやすい
  • コンポーネントをラップする必要がない
  • 最新のReact機能との親和性が高い

4. コンポーネントの接続例


以下のように、CounterWithHooksコンポーネントをアプリに組み込むことで、ReactとReduxの連携を確認できます:

src/App.js:

import React from 'react';
import CounterWithHooks from './features/counter/CounterWithHooks';

function App() {
  return (
    <div>
      <h1>Redux Counter App</h1>
      <CounterWithHooks />
    </div>
  );
}

export default App;

5. 動作確認


アプリケーションを起動し、状態の変更が反映されることを確認します:

npm start

まとめ


ReactコンポーネントとReduxを接続することで、状態をシームレスに管理できるようになりました。Hooksを使用することで、現代的な開発スタイルを採用できる点が魅力です。次は、状態更新の流れをより深く理解していきましょう。

状態更新の流れを理解する


Reduxの真髄は、アプリケーションの状態を一元管理し、その変更の流れを明確にすることです。このセクションでは、Reduxにおける状態更新のプロセスをステップごとに解説します。

1. 状態更新の全体フロー


Reduxでは、状態更新の流れが以下のように進行します:

  1. アクションのディスパッチ: ユーザー操作やイベントによってアクションが送信される。
  2. リデューサーで状態を変更: アクションに基づいてリデューサーが新しい状態を計算する。
  3. ストアが状態を更新: リデューサーから返された新しい状態をストアが保持する。
  4. UIが状態を反映: ストアが通知を発し、Reactコンポーネントが再描画される。

2. アクションのディスパッチ


Reactコンポーネントからアクションを送信することで、状態更新が始まります。

例: カウントをインクリメントするアクションの送信

import { useDispatch } from 'react-redux';
import { increment } from './counterSlice';

function Counter() {
  const dispatch = useDispatch();

  return (
    <button onClick={() => dispatch(increment())}>Increment</button>
  );
}

3. リデューサーによる状態変更


アクションがディスパッチされると、Reduxは該当するリデューサーを呼び出します。

例: インクリメントアクションに対応するリデューサー

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
  },
});

ポイント

  • リデューサーは現在の状態とアクションを受け取り、新しい状態を返します。
  • 状態の変更はイミュータブルな方法で行う必要がありますが、Redux Toolkitはこの処理を簡略化しています。

4. ストアが状態を更新


リデューサーが返した新しい状態は、ストアに保存されます。ストアは変更が発生すると、登録されたリスナーに通知します。

5. UIの更新


Reactでは、useSelectorフックを使用して、ストアの状態をリアルタイムで監視します。状態が変更されると、Reactコンポーネントが再描画されます。

例: カウンター値をUIに反映

import { useSelector } from 'react-redux';

function CounterDisplay() {
  const count = useSelector((state) => state.counter.value);

  return <h1>Count: {count}</h1>;
}

6. データフローの全体図


以下は、状態更新のプロセスを示した全体図です:

  • ユーザー操作 → アクション送信
  • アクション → リデューサー呼び出し
  • リデューサー → 新しい状態を計算
  • ストア → 状態を更新しリスナーに通知
  • Reactコンポーネント → 再描画

例: 状態更新の流れを体験する


以下のカウンターアプリで、状態更新の流れを試してみましょう:

App.js:

import React from 'react';
import Counter from './features/counter/Counter';
import CounterDisplay from './features/counter/CounterDisplay';

function App() {
  return (
    <div>
      <Counter />
      <CounterDisplay />
    </div>
  );
}

export default App;

アプリを起動し、ボタンをクリックすると、状態更新の一連の流れを確認できます。

まとめ


Reduxの状態更新フローは、アクションのディスパッチからリデューサーでの計算、ストアへの保存、UIの再描画まで明確に定義されています。この流れを理解することで、予測可能で安定したアプリケーションを構築できます。次は、開発ツールを使ったデバッグ方法を学びます。

開発ツールを使ったデバッグの方法


Reduxを利用する際、状態の変更やアクションの流れを可視化することは、バグの特定やアプリケーションの理解を深める上で非常に重要です。このセクションでは、Redux DevToolsを使用したデバッグ方法を解説します。

1. Redux DevToolsとは


Redux DevToolsは、ブラウザ拡張機能として提供されるツールで、以下の機能を備えています:

  • アクションの履歴確認: ディスパッチされたアクションの順序を可視化
  • 状態のスナップショット: 状態の変更を時系列で追跡
  • 時間操作: アクションを「巻き戻し」や「再生」することで状態を再現

2. Redux DevToolsの設定


Redux DevToolsを使用するには、プロジェクトにツールの設定を追加します。

DevTools拡張機能のインストール

  1. お使いのブラウザにRedux DevToolsをインストールします:

Reduxストアの設定


ストアにDevToolsのサポートを追加します。

src/store.js:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
  devTools: process.env.NODE_ENV !== 'production', // 本番環境では無効化
});

export default store;

これで、Redux DevToolsを使用する準備が整いました。

3. Redux DevToolsでの確認方法


アプリケーションを起動し、ブラウザでRedux DevToolsを開きます:

npm start

ブラウザの開発者ツールに「Redux」タブが追加されていることを確認してください。

状態の確認

  • Stateタブ: 現在の状態をツリー形式で表示します。
  • Diffビュー: 状態の変化を確認できます。

アクションの履歴追跡

  • Actionsタブ: ディスパッチされたアクションが時系列でリスト表示されます。
  • 各アクションをクリックすると、対応する状態が表示されます。

時間操作機能

  • Time Travel: スライダーを動かして過去の状態に戻すことができます。
  • 状態の再現性をテストする際に非常に便利です。

4. サンプルアプリでの活用例


以下のカウンターアプリでRedux DevToolsの動作を試してみましょう:

Counter.js:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

export default Counter;

アクションをディスパッチした後、DevToolsの「Actions」タブでアクションの履歴を確認し、「State」タブで状態の変化を追跡してください。

5. トラブルシューティング


Redux DevToolsが動作しない場合、以下を確認してください:

  • ストアのdevTools設定が正しいか。
  • ブラウザ拡張機能が有効になっているか。
  • 開発環境(process.env.NODE_ENV)で実行しているか。

まとめ


Redux DevToolsを使用することで、状態管理の動作を詳細に追跡でき、バグの特定やアプリケーションの最適化が容易になります。このツールを活用することで、Reduxの学習効率も向上します。次は、サンプルプロジェクトに機能を追加し、応用的な使い方を学びましょう。

応用編:機能拡張とカスタマイズ


ここでは、サンプルプロジェクトに新しい機能を追加し、Reduxをさらに深く学びます。今回の例では、To-Doリスト機能を作成して、Reduxを活用した状態管理の応用を体験します。

1. To-Doリスト機能の概要


この機能では、以下を実現します:

  • 新しいタスクを追加
  • タスクの完了状態を切り替え
  • タスクを削除

2. Reduxスライスの作成


まず、To-Doリストを管理するためのReduxスライスを作成します。

src/features/todo/todoSlice.js:

import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push({ id: Date.now(), text: action.payload, completed: false });
    },
    toggleTodo: (state, action) => {
      const todo = state.find((todo) => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    deleteTodo: (state, action) => {
      return state.filter((todo) => todo.id !== action.payload);
    },
  },
});

export const { addTodo, toggleTodo, deleteTodo } = todoSlice.actions;
export default todoSlice.reducer;

3. ストアにリデューサーを登録


To-Doスライスをストアに追加します。

src/store.js:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todoReducer from './features/todo/todoSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todoReducer,
  },
});

export default store;

4. To-DoリストのUIコンポーネントを作成


Reactコンポーネントを作成して、To-Doリストを表示し操作できるようにします。

src/features/todo/TodoList.js:

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo, deleteTodo } from './todoSlice';

function TodoList() {
  const [text, setText] = useState('');
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    if (text.trim()) {
      dispatch(addTodo(text));
      setText('');
    }
  };

  return (
    <div>
      <h2>To-Do List</h2>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new task"
      />
      <button onClick={handleAddTodo}>Add</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <span
              onClick={() => dispatch(toggleTodo(todo.id))}
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none',
                cursor: 'pointer',
              }}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch(deleteTodo(todo.id))}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

5. コンポーネントをアプリに組み込む


作成したTo-Doリストコンポーネントをアプリに組み込みます。

src/App.js:

import React from 'react';
import TodoList from './features/todo/TodoList';

function App() {
  return (
    <div>
      <h1>Redux To-Do App</h1>
      <TodoList />
    </div>
  );
}

export default App;

6. 機能をテストする


アプリを起動し、以下を確認してください:

  • タスクを追加できる。
  • タスクをクリックすると、完了状態が切り替わる。
  • タスクを削除できる。
npm start

7. 応用ポイント

  • フィルタ機能の追加: 完了済みタスクや未完了タスクのみを表示する機能を追加できます。
  • ローカルストレージとの連携: アプリを閉じてもTo-Doリストが保持されるようにできます。

まとめ


To-Doリスト機能の実装を通じて、Reduxを使った状態管理の応用を学びました。このように、実践的な例を通じてReduxの活用スキルを高めていきましょう。次は、この記事全体のまとめに進みます。

まとめ


本記事では、ReactとReduxの基本を学びながら、サンプルプロジェクトを通じて状態管理の流れや実装方法を解説しました。Reduxを用いることで、アプリケーションの状態を一元管理し、予測可能で効率的な開発が可能となります。

基本的なカウンターアプリの構築から始め、応用例としてTo-Doリスト機能の拡張を行い、ReactコンポーネントとReduxの連携や開発ツールを使ったデバッグ方法についても学びました。これにより、Reduxを使ったプロジェクト開発の全体像を理解できたはずです。

Reduxの概念とツールを習得することで、複雑な状態管理が必要な大規模アプリケーションでも柔軟に対応できるスキルが身につきます。この知識を活用し、さらに高度なプロジェクトに挑戦してください。

コメント

コメントする

目次