Yanonoblog!

こつこつと

Redux ストアの管理とスライスの使い方

はじめに

Reduxの公式ドキュメントに沿って概要を押さえつつ、気になった部分を深掘って整理しています。

本記事では 、Reduxのサンプルアプリを通してストアの管理とスライスの使い方についてまとめていきます。

Redux サンプルアプリのディレクトリ構成

下記は、前回作成したサンプルアプリのメイン構成です。

/src
│
├── index.js    # アプリの開始点
├── App.js      # 最上位の React コンポーネント
│
├── /app
│   ├── store.js    # Redux ストア インスタンスを作成
│
├── /features
│   ├── /counter
│   │   ├── Counter.js       # カウンター機能の UI を表示する React コンポーネント
│   │   ├── counterSlice.js  # カウンター機能の Redux ロジック

app/store.js

下記のコード例では、configureStoreにreducerオブジェクトを渡しています。

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

export default configureStore({
  reducer: {
    counter: counterReducer
  }
})

configureStoreは、Redux Toolkitの関数で、リデューサーを元に新しいストアを生成します。

ストアとは、状態を保持し、その状態に対してアクションをディスパッチできるオブジェクトのことです。

オブジェクトのキーがcounterで、値がcounterReducerとなっています。

上記のコードでは、アプリケーションの状態の中にcounterという領域を作り、その領域の管理はcounterReducerというリデューサーで行われています。

**counterの状態を管理するロジックを一つのリデューサーにまとめることで、コードの管理が容易になります。**

features/counter/counterSlice.js

このファイルは「カウンタ」という機能のための Redux のロジックが書かれています。

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

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

基本的にはreducersにメソッドを定義し、分割代入で各メソッドをエクスポートすることで外部のコンポーネントでインポートして呼び出すことができます。

呼び出したreducerはdispatchすることで状態更新するのが基本的な活用方法になります。

Redux ToolkitのcreateSlice関数にreducerを定義します。

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

createSlice関数は以下のパラメータをとります。

const slice = createSlice({
  name: string,
  initialState: any,
  reducers: Object<string, Function>,
  extraReducers?: (builder) => void | Object<string, Function>
});
  • name スライスの名前。生成されるアクションタイプの一部として使用されます。
  • initialState スライスの初期状態。
  • reducers オブジェクト形式で、アクションタイプごとの状態更新処理を記述します。
  • extraReducers(任意オプション) createAsyncThunk などで生成された非同期アクションの結果に対するリデューサーを追加できます。

Sliceはアクションクリエーターを生成します。

console.log(counterSlice.actions.increment())
// {type: "counter/increment"}

Reduxのルール

Reducersは以下のルールに常に従う必要があります。

  • 状態とアクションの引数に基づいて新しい状態の値を計算する
  • 既存の状態を変更せずに既存の状態をコピーし、コピーした値に対して変更を加えることで更新を行う
// ✅ コピーすることで不変な値として状態管理する
return {
  ...state,
  value: 123
}

参考

続く…

コメント

本記事の内容は以上になります!

書籍の続きのアウトプットも随時更新したいと思います。


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky