Reduxでフロントで扱うデータを一元管理する
Reduxは状態管理のためのReact向けライブラリです。 シングルページアプリケーションでは複数の画面(コンポーネント)にわたってデータを共有する場面が出てきます。 また、親子コンポーネント間のデータの受け渡しについても、コンポーネントの階層が深くなってくると煩雑になってきます。
Reduxを用いると、そのようなデータをひとつの場所(ストア)に置くことで、 アプリケーション内でのデータの状態の見通しを良くすることができます。
Reduxを利用する
ReactにもuseStateといった状態を保持するためのメソッドがあります。 ただし、管理するデータの数が多くなってくると、どこに状態を保持しているのか、どこから更新されるのかがわかりづらくなります。
Reduxによりデータの状態変更の流れを手続き化することで、 データが意図せず変更されることを防ぎ安全性の高いアプリケーションを作ることが可能です。
npm i react-redux
Reduxの役割
Reduxの役割は、状態を保持し、値の更新を検知し、必要なUIパーツとデータを抽出し、更新し、フォームの値と同期し、 初期化するという一連の流れをいちいち書くことなく利用できるようにすることです。
Store
データを入れておく箱のようなものです。 Reduxで扱うすべてのデータはここに集約されます。
1import { Provider, createStore } from 'react-redux'
2const store = createStore()
3
4ReactDOM.render(
5 <Provider store={store}>
6 <Main />
7 </Provider>,
8 document.getElementById('root')
9);
Providerコンポーネントは、Reduxから提供されるstoreを配置するもので、アプリケーションのコンポーネントを内包するようにします。
Actions
データ操作の名前です。「Todoリストに追加」「買い物カゴから削除」などの操作をaddTodoList
やdeleteFromShoppingCart
というように文字列で表現します。
1const increment = () => {
2 return {
3 type: ‘addTodoList’
4 }
5}
6store.dispatch(increment())
Actionを実行させるには、Storeのdispatchメソッドにactionを指定します。 また、Actionの文字列を生成するメソッドをAction Creatorと呼びます。
Reducers
Storeの状態を変更するメソッドです。 Actionから直接Storeを変更することはできません。Storeを変更する際には必ずReducerを呼ぶ必要があります。
1export default function Reducer(state, action) {
2 switch (action.type) {
3 case 'addTodoList': {
4 return {
5 ...state,
6 todos: [
7 ...state.todos,
8 {
9 task: action.payload,
10 }
11 ]
12 }
13 }
14 default:
15 return state
16 }
17}
createSliceによる書き方
最近のReduxの導入方法としてcreateSliceというのがあります。 前述のようにReduxで状態管理するためにはReducerやActionのファイルを作らなければなりません。 createSliceによって1つのファイルにReducer、Actionを定義することができ、見通しが良くなります。
createSliceを作成する
createSliceを使うために、次のコマンドを実行します。
npm i @reduxjs/toolkit
1import { configureStore } from '@reduxjs/toolkit'
2import todosReducer from './Components/Todo/todosSlice'
3
4export const store = configureStore({
5 reducer: {
6 todos: todosReducer
7 }
8})
storeを作成、使用するReducerを登録します。この場合、storeのtodos配下でデータを管理します。
1import { createSlice } from '@reduxjs/toolkit'
2
3export const todosSlice = createSlice({
4 name: 'todos',
5 initialState: {
6 id: '',
7 title: '',
8 body: []
9 },
10 reducers: {
11 addCard: (state, action) => {
12 state.body.unshift({
13 sort: "0", col1: action.payload.col1, col2: action.payload.col2
14 })
15 return state
16 },
17 changeCard: (state, action) => {
18 const data = action.payload
19 state.body[data.order][data.side] = data.value
20 return state
21 },
22 deleteCard: (state, action) => {
23 const order = action.payload
24 state.body.splice(order, 1)
25 return state
26 },
27 }
28})
29
30export const {
31 addCard,
32 changeCard,
33 deleteCard,
34 sortCard,
35 changeTitle
36} = cardsSlice.actions
37
38export default cardsSlice.reducer
storeの初期状態と、Reducerを定義します。 Reducerは、状態(state)とパラメータ(action)受け取って新しい状態を返します。 ここで定義したReducerをexportすることで、他のコンポーネントから呼び出すことができます。
なお、上記コードにはActionが無いように見えますが、 createSliceが
store名/reducer名
をキーとするActionを自動的に生成してくれています。
非同期処理
外部APIを呼ぶ非同期通信など、非同期処理を行う場合は、createAsyncThunkというメソッドを呼び出します。
1import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2import axios from 'axios'
3
4export const todosSlice = createSlice({
5 ...(省略)
6 extraReducers: {
7 'todos/apiGetCards/fulfilled': (state, action) => {
8 return {
9 ...action.payload
10 }
11 }
12 }
13})
14
15export const apiGetTodos = createAsyncThunk(
16 'todos/apiGetTodos',
17 async (id) => {
18 const response = await axios.get(\`https://www.example.com/todos/\${id}\`)
19 return {
20 id: response.data.id,
21 title: response.data.title,
22 body: JSON.parse(response.data.body)
23 }
24 }
25)
createAsyncThunkの第一引数にアクション名を定義し、第二引数に非同期処理を記述します。
この非同期処理の状態を監視し、その状態に応じてstateを変更することができます。
状態にはfulfilled
(成功),
rejected
(失敗), pending
(待機中)の三種類があります。