2021-07-10 投稿 2022-01-06 更新

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リストに追加」「買い物カゴから削除」などの操作をaddTodoListdeleteFromShoppingCartというように文字列で表現します。

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 (待機中)の三種類があります。