💭

Redux Toolkitを使ってみる

2023/12/10に公開

概要

ReactにはuseContextとかあるが、大規模になるとReduxの方がいいらしい!?が使ったことないので、試してみる。どうやらRedux Toolkitを使ってみることにした。

ディレクトリ構成

今回のTODOリストにあたり、下記のようなディレクトリ構成とする。

src/
├── features
│       └── todoSlice.js
├── components
│       └── Form.js
│       └── TodoList.js
|── App.js
|── index.js

プロジェクト作成/ライブラリをインストール

プロジェクトの作成から、Redux TookkitやReduxをインストールしてみる。
React Toolkitの公式はこちら
https://redux-toolkit.js.org/introduction/getting-started

プロジェクトを作成。今回はReactのみ。とりあえずTODOリストを作ってみるので下記のコマンドを打つ。

npx create-react-app redux-todo

プロジェクトを作成したらRedux ToolkitとReduxをインストール

npm install @reduxjs/toolkit
npm install react-redux

とりあえず準備は完了。次から設定へ。

アプリケーション全体で利用できるようにする

アプリケーション全体で状態管理ができるように設定する必要があるのでまずは、
configureStoreでstoreを用意して、storeを設定するためのProviderをインポートする。

index.js
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';

const store =configureStore({
  reducer:{}
})

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

上記のようにconfigureStorestoreのオブジェクトを作る。
reducer:{}としているがこちらには後程、Reducerを指定する。
ここで指定するとReducerがないというエラーになるので。

<App/>Providerで囲んで、storeに作成したstoreインスタンスを指定する。これでアプリケーション全体で利用できるようになる。

このあとreducersとaction creatorsを作成する。

createSliceでreducersとaction creatorsを生成する

createSliceは、Redux Toolkitに含まれるヘルパー関数で、Reduxアプリケーションのリデューサー(reducers)とアクションクリエーター(action creators)を効率的に生成するための便利な方法を用意してくれている。

通常、Reduxアプリケーションでは、アクションやリデューサーの定義が冗長になりがちでした。createSliceはこの冗長性を削減し、コードの記述を簡素化します。

公式サイトに合わせて、featuresフォルダを作成して、その配下にファイルを作成。

createSliceでオブジェクトを作成

まずはcreateSliceを作成して、nameinitialStatereducersのプロパティを指定する。reducersには、実際の処理内容を記述する

/features/TodoSlice.js
import { createSlice } from "@reduxjs/toolkit";

const todoSlice = createSlice({
    name:"",
    initialState:{},
    reducers:{}
});

詳細の設定

今回は以下のように設定する。

  • nameはtodos
  • initisalStateはTODOを配列で管理する
  • reducersにはTODOの追加と削除のActionsを設定する
/features/TodoSlice.js
import { createSlice } from "@reduxjs/toolkit";

const todoSlice = createSlice({
    name:"todos",
    initialState:{
       value: []
    },
    reducers:{
        addTodo:(state,action)=>{
            state.value.push(action.payload);
        },
        removeTodo:(state,action)=>{
            const index = state.value.findIndex(todo=>todo.id===action.payload.id);
            if(index!==-1){
                state.value.splice(index,1);
            }
        }
    }
});

nameについて

nameについては任意で構わない。
Redux ToolkitのcreateSliceメソッドで作成されたsliceのnameは、Redux DevToolsのデバッグやトレース時に使用される。
このnameは生成されたReduxアクションやReduxのステートツリーの構造をデバッグツールで追跡する際に役立ちます。

initialStateについて

initialStatevalueを設定しているがvalueでなくてもいい。
Actionでstateからアクセスする際に利用するのでここが合っていればいい。
なので以下のようにhogeとしてもよい。

/features/TodoSlice.js
const todoSlice = createSlice({
    name:"todos",
    initialState:{
       hoge: []
    },
    reducers:{
        addTodo:(state,action)=>{
            state.hoge.push(action.payload);
        }
    }
});

その際に、コンポーネントなどでuseSelectorで状態を取得する際も以下のようになる。

 const todoList = useSelector(state => state.todos.hoge);

reduersについて

Redux Toolkitにおけるreducersは、Reduxストア内の状態を変更するための関数群です。reducersは純粋な関数であり、前の状態とアクションを受け取り、新しい状態を返します。

通常、Reduxアプリケーションでは複数のreducersを組み合わせて使用します。
Redux Toolkitでは、これを簡略化するためにcreateSliceというヘルパー関数が提供されている。
createSliceを使用すると、アクションクリエーター(action creators)、リデューサー(reducers)、およびアクションの型(action types)を一つのオブジェクトにまとめることができる。

Actionsについて

Reducerは前の状態とアクションを受け取り、新しい状態を生成します。stateはアプリケーションの変更を受ける前の状態を表し、actionは何が起こったのかを指定する。

addTodo関数でいえばstateは記事を追加する前の状態を受け取り、actionは新しい記事の内容をaction.payloadを通じて受け取れるので、それを配列に追加している。

ちなみに決まった値、例えばカウンターなどでクリックしたら+1づつ増えていく場合はactionが必要ないこともある。

reducers:{
        addTodo:(state)=>{
            state.value++;
        }
    }

ActionsとReducerをexport

色んなコンポーネントで利用できるように、exportする。

/features/TodoSlice.js
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer

configureStoreのReducerに設定する

exportしたReducerを使用できるように設定する。

状態管理するstoreconfigureStoreで作成したと思うが、こちらにreducerを指定できるので、下記のように指定する。

index.js
//インポートする
import todoReducer from './features/TodoSlice';

const store = configureStore({
  reducer:{
    todos:todoReducer
  }
})

todostodoReducerとして設定する。
todosの名前は任意なので分かりやすい名前にしたほうがいい。
こちらはuseSelectorでTODOの状態を取得する時にアクセスするときに利用する。

const todoList = useSelector(state => state.todos.value);

仮にhogeの場合は下記になる。

//configureStoreでhogeとして登録
const store = configureStore({
  reducer:{
    hoge:todoReducer
  }
})

//stateのhogeプロパティにアクセス。todoReducer(TodoSlice.js)のvalueから取得する
const todoList = useSelector(state => state.hoge.value);

コンポーネントで利用する

これで設定は完了です。
実際に呼び出してみる。今回はReduxでいろんなコンポーネントで呼び出したいので、登録する為のフォームをForm.jsというコンポーネントとTODOを一覧表示する・削除する為のコンポーネントのTodoList.jsというコンポーネントの2つを作成した。

TODOを登録するフォームのコンポーネント

ソースコードは以下。

components/Form.js
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { addTodo } from '../features/TodoSlice';

const Form = () => {
    const [text,setText] = useState('');

    const todoList = useSelector(state => state.todos.value);
    const dispatch = useDispatch();

    const addTodoHandler = (e) => {
        e.preventDefault();

        dispatch(addTodo(
            {
                id:todoList.length,
                text:text
            }
        ));

        setText('');
    }
    
  return (
    <div>
        <form onSubmit={(e)=>addTodoHandler(e)}>
            <input onChange={(e)=>setText(e.target.value)} value={text} type="text" />
            <button type="submit">追加</button>
        </form>
    </div>
  )
}

export default Form

useSelectorで状態管理を呼び出す

useSelectorはReact Reduxライブラリで提供される、Reactコンポーネント内でReduxのストアから状態を選択するためのフックである。
このフックは、Reduxストアの状態を取得し、コンポーネントが再レンダリングされる際に必要な状態のみを抽出するのに役立ちます。

通常、Reduxストアにはアプリケーション全体の状態が保存されるが、コンポーネントが必要なのは特定の一部の状態だけであることがあります。
このような場合、useSelectorを使用してReduxストアから必要な部分の状態を取得することができます。

const todoList = useSelector(state => state.todos.value);

上記のuseSelectorの引数は、index.jsconfigureStorereducerに登録したtodosに指定してあるtodoReducervalueにアクセスしている。

useDispatchで処理を通知する

useDispatchもまたReact Reduxライブラリで提供されるフックであり、Reduxストアにアクションをディスパッチ(送信)するために使用されます。

useDispatchを使用することで、ReactコンポーネントからReduxストアにアクションを送ることができる。

 dispatch(
    addTodo(
      {
        id:todoList.length,
        text:text
      }
 ));

上記はdispatchaddTodoにIDとフォームのテキストを設定して送ってる。

TODOを表示する/削除できるようにするコンポーネント

まずはソースコードは以下です。なるべく余計な表記はなくしています。

components/TodoList.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { removeTodo } from '../features/TodoSlice';

const TodoList = () => {
    const todoList = useSelector(state => state.todos.value);
    const dispatch = useDispatch();

    const deleteTodoHandler = (id) => () => {
        const result = window.confirm('削除しますか?');
        if (result) {
            dispatch(removeTodo({
                id: id
            }));
        }
    }

    if (todoList.length === 0) {
        return (
            <div className='no-todos'>Todoがありません</div>
        )
    }

    return (
        <ul className='todo-list'>
            {todoList.map((todo) => (
                <li key={todo.id}>
                    <p>{todo.text}</p>
                    <button onClick={deleteTodoHandler(todo.id)} className='delete-button'>削除</button>
                </li>
            ))}
        </ul>
    )
}

export default TodoList

useSelectorで現在のTODOの状態を取得して、map()で一覧を表示する。
また削除イベントで登録してあるremoveTodoを呼び出すために、useDispatchのオブジェクトを作成する。

削除ボタンは、deleteTodoHandlerにTODOのid番号を指定してdispatchを通してremoveTodoにid番号を渡す。
あとはReducerに登録されているActionsのremoveTodoに一致するidがあれば配列から削除されるという流れ。

複数の状態管理をしたい場合

今回は、TODOリストの状態管理をRedux Toolsで管理しましたが、他にも管理したい場合があるかと思います。例えばカウンターなどの値を状態管理したい場合は、以下の流れで追加する。

createSliceでカウンター用のreducersを生成する

まずはカウンター用のReducerを作成する。

features/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";

const CounterSlice = createSlice({
    name: "increment",
    initialState: {
        value: 0,
    },
    reducers: {
        increment: (state) => {
            state.value++;
        },
        decrement: (state) => {
            state.value--;
        }
    }
})

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

storeに登録する

createSliceでReducerとActionsCreatorを作成したら使えるようにstoreに登録する。

index.js
//インポートする
import counterReducer from './features/CounterSlice'

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

counter:counterReducerと登録する。これでカウンター用のReduxが使えるようになる。

まとめ

一旦、これでTODOの追加・削除とTODOの一覧表示はできた。
下記に公式のサイトにクイックスタートなど参考になります。

https://redux-toolkit.js.org/tutorials/quick-start
https://tekrog.com/how-to-use-slice#Store

Discussion