Redux Toolkitを使ってみる
概要
ReactにはuseContext
とかあるが、大規模になるとReduxの方がいいらしい!?が使ったことないので、試してみる。どうやらRedux Toolkitを使ってみることにした。
ディレクトリ構成
今回のTODOリストにあたり、下記のようなディレクトリ構成とする。
src/
├── features
│ └── todoSlice.js
├── components
│ └── Form.js
│ └── TodoList.js
|── App.js
|── index.js
プロジェクト作成/ライブラリをインストール
プロジェクトの作成から、Redux TookkitやReduxをインストールしてみる。
React Toolkitの公式はこちら
プロジェクトを作成。今回はReactのみ。とりあえずTODOリストを作ってみるので下記のコマンドを打つ。
npx create-react-app redux-todo
プロジェクトを作成したらRedux ToolkitとReduxをインストール
npm install @reduxjs/toolkit
npm install react-redux
とりあえず準備は完了。次から設定へ。
アプリケーション全体で利用できるようにする
アプリケーション全体で状態管理ができるように設定する必要があるのでまずは、
configureStoreでstoreを用意して、storeを設定するためのProviderをインポートする。
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>
);
上記のようにconfigureStore
でstore
のオブジェクトを作る。
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
を作成して、name
とinitialState
とreducers
のプロパティを指定する。reducers
には、実際の処理内容を記述する
import { createSlice } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name:"",
initialState:{},
reducers:{}
});
詳細の設定
今回は以下のように設定する。
- nameはtodos
- initisalStateはTODOを配列で管理する
- reducersにはTODOの追加と削除のActionsを設定する
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について
initialState
はvalue
を設定しているがvalue
でなくてもいい。
Actionでstate
からアクセスする際に利用するのでここが合っていればいい。
なので以下のようにhoge
としてもよい。
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
する。
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer
configureStoreのReducerに設定する
export
したReducerを使用できるように設定する。
状態管理するstore
をconfigureStore
で作成したと思うが、こちらにreducer
を指定できるので、下記のように指定する。
//インポートする
import todoReducer from './features/TodoSlice';
const store = configureStore({
reducer:{
todos:todoReducer
}
})
todos
にtodoReducer
として設定する。
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を登録するフォームのコンポーネント
ソースコードは以下。
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.js
でconfigureStore
でreducer
に登録したtodos
に指定してあるtodoReducer
のvalue
にアクセスしている。
useDispatchで処理を通知する
useDispatch
もまたReact Reduxライブラリで提供されるフックであり、Reduxストアにアクションをディスパッチ(送信)するために使用されます。
useDispatchを使用することで、ReactコンポーネントからReduxストアにアクションを送ることができる。
dispatch(
addTodo(
{
id:todoList.length,
text:text
}
));
上記はdispatch
でaddTodo
にIDとフォームのテキストを設定して送ってる。
TODOを表示する/削除できるようにするコンポーネント
まずはソースコードは以下です。なるべく余計な表記はなくしています。
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を作成する。
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に登録する。
//インポートする
import counterReducer from './features/CounterSlice'
const store = configureStore({
reducer:{
todos:todoReducer,
counter:counterReducer
}
})
counter:counterReducer
と登録する。これでカウンター用のReduxが使えるようになる。
まとめ
一旦、これでTODOの追加・削除とTODOの一覧表示はできた。
下記に公式のサイトにクイックスタートなど参考になります。
Discussion