🙄

ReduxとRedux Toolkit : Reactの状態管理を効率化

2024/08/18に公開

Reduxとは

Reduxは、JavaScriptアプリケーション全体の状態を一元管理するためのライブラリです。Reactとの組み合わせが特に一般的ですが、他のフレームワークやライブラリと組み合わせて使用することもできます。

Reduxの3つの主要原則

  1. 単一のソース・オブ・トゥルース (Single Source of Truth):
    Reduxでは、アプリケーション全体の状態を一つの「Store」で管理します。これにより、全てのコンポーネントが一貫したデータを参照できます。

  2. 状態は読み取り専用 (State is Read-Only):
    状態は直接変更せず、「Action」と呼ばれるオブジェクトを介してのみ更新されます。これにより、状態の予測可能性が高まります。

  3. 純粋関数による変更 (Changes are made with Pure Functions):
    状態の変更は「Reducer」と呼ばれる純粋関数を通じて行われます。純粋関数とは、同じ入力に対して常に同じ出力を返す関数のことです。

Reduxを利用するメリット

Reduxを利用することで以下のメリットが得られます:

  • 一貫性のある状態管理: 単一のStoreで状態を管理することで、アプリケーションの一貫性を保ちやすくなります。
  • デバッグが容易: Redux DevToolsを使うことで、状態の変遷を時間軸で追跡できます。
  • スケーラビリティ: 大規模なアプリケーションでも、状態の管理が整理され、拡張しやすくなります。

Reduxを用いたStoreの値の更新方法

Reduxで状態を更新するには、以下の3つのステップを踏みます:

  1. Actionの定義: 状態を変更するための命令(アクション)を定義します。
  2. Reducerの作成: Actionに応じて状態をどのように変更するかを定義するReducer関数を作成します。
  3. Storeの更新: dispatchメソッドを使ってActionをStoreに送り、Reducerが状態を更新します。

以下は基本的な例です。

// Actionの定義
const increment = () => {
  return {
    type: 'INCREMENT'
  };
};

// Reducerの作成
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

// Storeの作成
const store = createStore(counterReducer);

// Storeの更新
store.dispatch(increment());
console.log(store.getState()); // { count: 1 }

Redux Toolkitとは

Redux Toolkitは、Reduxの公式開発チームが提供するライブラリで、Reduxの使用を容易にしつつ、ベストプラクティスを自動的に取り入れるように設計されています。

Redux Toolkitの特徴

  • 簡潔な構文: Redux Toolkitは、スライス (slice) という概念を導入し、ActionとReducerの定義をまとめて行えるようにしています。
  • デフォルトでImmerをサポート: 状態の不変性を簡単に維持できます。
  • 非同期処理のサポート: createAsyncThunkで非同期処理が簡単に扱えます。

Redux Toolkitを用いたStoreの値の更新方法

Redux Toolkitを使うと、スライスを利用して状態の更新が簡潔に書けます。

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

// Sliceの作成
const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1;
    }
  }
});

// Storeの作成
const store = configureStore({
  reducer: counterSlice.reducer
});

// Storeの更新
store.dispatch(counterSlice.actions.increment());
console.log(store.getState()); // { count: 1 }

別のUIコンポーネントからのStoreの更新

Redux Toolkitを使った場合でも、複数のUIコンポーネントからStoreを更新できます。例えば、以下のように2つのコンポーネントを用意し、dispatchメソッドを使用して状態を更新します。

まず、Storeの設定は以下のようにします。

// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

// Sliceの作成
const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state, action) => {
      // state = 現在値, action = { type: "counter/increment", payload: {value: 1} }
      state.count += action.payload.value;
    },
    decrement: (state, action) => {
      // state = 現在値, action = { type: "counter/decrement", payload: {value: 1} }
      state.count -= action.payload.value;
    }
  }
});

// Actionsのエクスポート
export const { increment, decrement } = counterSlice.actions;

// Storeの作成
const store = configureStore({
  reducer: counterSlice.reducer
});

export default store;

次に、Reactアプリケーション内でReduxを使用するために、Providerを使ってStoreをアプリ全体に渡します。

// App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';
import CounterControls from './CounterControls';

function App() {
  return (
    <Provider store={store}><!-- Providerにstoreを登録 -->
      <div>
        <Counter />
        <CounterControls />
      </div>
    </Provider>
  );
}

export default App;

Counterコンポーネントは現在の状態を表示します。

// Counter.js
import React from 'react';
import { useSelector } from 'react-redux';

function Counter() {
  const count = useSelector((state) => state.count); // countはスライスで定義したname

  return (
    <div>
      <h2>Count: {count}</h2>
    </div>
  );
}

export default Counter;

CounterControlsコンポーネントは、dispatchを使って状態を更新します。

// CounterControls.js
import React from 'react';
import { useDispatch } from 'react-redux';
import { increment, decrement } from './store';

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

  return (
    <div>
      <button onClick={() => dispatch(increment({value: 1}))}>+1</button>
      <button onClick={() => dispatch(decrement({value: 1}))}>-1</button>
      <button onClick={() => dispatch(increment({value: 2}))}>+2</button>
      <button onClick={() => dispatch(decrement({value: 2}))}>-2</button>
    </div>
  );
}

export default CounterControls;

この構成により、CounterControlsコンポーネントのボタンをクリックするたびに、Counterコンポーネントが表示するカウントの値が更新されます。Redux Toolkitを使うことで、状態管理がシンプルかつ効率的に行えることが分かります。

Redux Toolkitを用いた非同期処理でのStoreの更新方法

Redux Toolkitでは、createAsyncThunkを使って非同期処理を簡単に扱えます。以下はその例です。

import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 非同期Thunkの定義
export const fetchData = createAsyncThunk(
  'data/fetchData',
  async () => {
    const response = await fetch('/api/data');
    return response.json();
  }
);

// Sliceの作成
const dataSlice = createSlice({
  name: 'data',
  initialState: { value: [], status: 'idle' },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchData.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchData.fulfilled, (state, action) => {
        // fetchDataの返り値がaction.payloadに格納されている。
        state.value = action.payload;
        state.status = 'succeeded';
      })
      .addCase(fetchData.rejected

, (state) => {
        state.status = 'failed';
      });
  }
});

// Storeの作成
const store = configureStore({
  reducer: dataSlice.reducer
});

// 非同期処理の実行
store.dispatch(fetchData());

非同期処理の状態ごとに処理を分岐させる方法

上記の例のように、createAsyncThunkを使うことで、非同期処理の進行状態(pending, fulfilled, rejected)に応じた状態の更新を簡単に行えます。これにより、非同期処理のローディング状態やエラーハンドリングが容易になります。

まとめ

ReduxとRedux Toolkitは、Reactアプリケーションの状態管理を強力かつ効率的に行うためのツールです。特にRedux Toolkitを使うと、従来のReduxの複雑さが軽減され、コードの可読性と保守性が向上します。また、別のUIコンポーネントから状態を更新できる点や、非同期処理の管理が簡単になる点も大きなメリットです。

Discussion