環境は
react : 16.12
typescript : 3.7
@reduxjs/toolkit : 1.2
react-redux : 7.1
でお送りします。
この記事でやること
基本的に以前作成したTodoアプリにRedux Toolkitを導入します。
Todoアプリについては下記記事を参照ください。
なぜReduxを使うのか
前回作成したTodoアプリの規模のアプリケーションであれば、このままでも問題はないのですが、コンポーネントが増えるとデータを次々とPropsで渡すということにり管理が難しくなります。
またステートを変更する関数はリストに作成しましたが、ここでよかったのかという問題もあります。
この辺りを解決する手段としてReduxというライブラリがあります。
Reduxのフロー図
かなりざっくりなのですが、Reduxのステート更新の流れです。
View(コンポーネント)からActionを実行して、Reducerを通してStateが更新され、Viewに反映されます。
Reduxで作る場合はこの処理を一つ一つ書いていく必要があるのですが、Redux Toolkit を使用することで、基本的な流れは変わりませんが、ある程度処理をまとめて書くことができるというイメージです。

ライブラリのインストール
最初にライブラリのインストールをしましょう。
@reduxjs/toolkitの他にreact-reduxと、TypeScriptの場合@types/react-reduxが必要です。
$ npm install --save @reduxjs/toolkit react-redux @types/react-reduxRedux Toolkit の準備
Redux Toolkit を使う為の準備をしていきます。
rootReducer.tsファイルを下記のように新規作成します。
import { combineReducers } from '@reduxjs/toolkit'
const rootReducer = combineReducers({})
export type RootState = ReturnType<typeof rootReducer>
export default rootReducercombineReducersは今は空ですが、後ほど作成するReducerを入れることになります。
type RootStateはステートの型指定する時に使います。
次はstore.tsです。
configureStoreに先ほど作成したrootReducerを登録してstoreとして使えるようにします。
import { configureStore } from '@reduxjs/toolkit'import rootReducer from './rootReducer'
const store = configureStore({ reducer: rootReducer})
export type AppDispatch = typeof store.dispatch
export default store次に共通のストアを各コンポーネントで使えるようにする必要があります。
トップコンポーネントに設定するので、index.tsxを次のように編集しましょう。
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import * as serviceWorker from './serviceWorker';import { Provider } from 'react-redux'import store from './store'
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root'))Providerとstoreを読み込んで、Appコンポーネントを囲む部分を追記しています。
これで Redux Toolkit の準備は完了です。
ここまでは基本そのまま使う部分だと思いますので、こういうものだと思って進めていきましょう。
createSlice
createSliceという機能を使うことで、State、Reducer、Actionを一気に作成することができます。
今回はmodulesというディレクトリを作りその中にtasksModule.tsファイルを作成しました。
import { createSlice, PayloadAction } from '@reduxjs/toolkit'import { Task } from '../Types'
type State = { count: number tasks: Task[]}
const initialState: State = { count: 2, tasks: [ { id: 2, title: '次のTodo', done: false },{ id: 1, title: '最初のTodo', done: true } ]}
const tasksModule = createSlice({ name: 'tasks', initialState, reducers: { addTask (state: State, action: PayloadAction<string>) { state.count++
const newTask: Task = { id: state.count, title: action.payload, done: false }
state.tasks = [newTask, ...state.tasks] }, doneTask (state: State, action: PayloadAction<Task>) { const task = state.tasks.find(t => t.id === action.payload.id) if (task) { task.done = !task.done } }, deleteTask (state: State, action: PayloadAction<Task>) { state.tasks = state.tasks.filter(t => t.id !== action.payload.id ) } }})
export const { addTask, doneTask, deleteTask} = tasksModule.actions
export default tasksModule- name
- このcreateSliceを識別するための名前を付けます。
- initialState
- ステートの初期データを入れます。
ここではあらかじめ作成したinitialStateをそのまま入れてます。 - reducers
- ステートを変更する為の処理はここに書きます。
各コンポーネントで書いていた関数をこちらに移動しましょう。
ここで設定する関数は、第一引数にstateを受け取り、実行時に渡した引数は第二引数にactionとして受け取ります。実際の値はaction.payloadで取り出します。
設定したReducerはtasksModule.actions.addTask()で実行できます。
コンポーネントで使いやすいようにtasksModule.actionsでエクスポートしておきましょう。
このようにすることでコンポーネントからaddTask()だけで実行できるようになります。
作成したら先ほど空だったrootReducerに入れておきましょう。
import tasksModule from './modules/tasksModule'
const rootReducer = combineReducers({ tasks: tasksModule.reducer})コンポーネントからステートにアクセスする
createSliceで作成したステートにアクセスするには、react-reduxのuseSelectorを使います。
TaskListコンポーネントのtasksを置き換えてみましょう。
この部分はPropsで受け取っていましたので、Propsは必要なくなりました。
import React from 'react'import TaskItem from './TaskItem'import { useSelector } from 'react-redux'import { RootState } from '../rootReducer'
const TaskList: React.FC = () => { const { tasks } = useSelector((state: RootState) => state.tasks)
return ( <div className="inner"> { tasks.length <= 0 ? '登録されたTODOはありません。' : <ul className="task-list"> { tasks.map(task => ( <TaskItem key={task.id} task={task} /> )) } </ul> } </div> )}
export default TaskListコンポーネントの関数をActionに置き換える
次はコンポーネントの書いていた関数をcreateSliceで作成したActionに置き換えます。
タスクの登録処理を書いていたTaskInputから編集します。
Actionはdispatchの引数に入れることで実行できます。
react-reduxのuseDispatchでdispatchを使えるようにしましょう。
handleSubmitの処理の部分はReducerに移したので、その部分をdispatch(addTask(inputTitle))に変更します。
import React, { useState } from 'react'import { useDispatch } from 'react-redux'import { addTask } from '../modules/tasksModule'
const TaskInput: React.FC = () => { const dispatch = useDispatch()
const [ inputTitle, setInputTitle ] = useState('')
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputTitle(e.target.value) }
const handleSubmit = () => { dispatch(addTask(inputTitle)) setInputTitle('') }
return ( <div className="input-form"> <div className="inner"> <input type="text" className="input" value={inputTitle} onChange={handleInputChange} placeholder="TODOを入力してください。" /> <button onClick={handleSubmit} className="btn is-primary">追加</button> </div> </div> )}
export default TaskInput次はTaskItemコンポーネントの関数をdoneTaskとdeleteTaskアクションに変更します。
やってることはTaskInputとほとんど変わらないですね。
import React from 'react'import { Task } from '../Types'import { useDispatch } from 'react-redux'import { doneTask, deleteTask } from '../modules/tasksModule'
type Props = { task: Task}
const TaskItem: React.FC<Props> = ({ task }) => { const dispatch = useDispatch()
return ( <li className={task.done ? 'done' : ''}> <label> <input type="checkbox" className="checkbox-input" onClick={() => dispatch(doneTask(task))} defaultChecked={ task.done } /> <span className="checkbox-label">{ task.title }</span> </label> <button onClick={() => dispatch(deleteTask(task))} className="btn is-delete" >削除</button> </li> )}
export default TaskItemPropsが必要なくなったのでApp.tsxも編集しておきましょう。
import React from 'react'import TaskList from './components/TaskList'import TaskInput from './components/TaskInput'import './App.css'
const App: React.FC = () => { return ( <div> <TaskInput /> <TaskList /> </div> )}
export default App以上です。Reduxで挫折した人もRedux Toolkitなら入りやすいのではないでしょうか。
Advanced Tutorial: Redux Toolkit in Practice
Redux の記述量多すぎなので、 Redux の公式ツールでとことん楽をする。 ( Redux Toolkit)
この記事の動画(Youtube)版はこちら!
