sagara.inkITエンジニアのまとめノート

Reduxでフロントで扱うデータを一元管理する

Reduxは状態管理のためのReact向けライブラリです。 シングルページアプリケーションでは複数の画面(コンポーネント)にわたってデータを共有する場面が出てきます。 また、親子コンポーネント間のデータの受け渡しについても、コンポーネントの階層が深くなってくると煩雑になってきます。

Reduxを用いると、そのようなデータをひとつの場所(ストア)に置くことで、 アプリケーション内でのデータの状態の見通しを良くすることができます。

Reduxを利用する

ReactにもuseStateといった状態を保持するためのメソッドがあります。 ただし、管理するデータの数が多くなってくると、どこに状態を保持しているのか、どこから更新されるのかがわかりづらくなります。

Reduxによりデータの状態変更の流れを手続き化することで、 データが意図せず変更されることを防ぎ安全性の高いアプリケーションを作ることが可能です。

console
npm i react-redux

Reduxの役割

Reduxの役割は、状態を保持し、値の更新を検知し、必要なUIパーツとデータを抽出し、更新し、フォームの値と同期し、 初期化するという一連の流れをいちいち書くことなく利用できるようにすることです。

Store

データを入れておく箱のようなものです。 Reduxで扱うすべてのデータはここに集約されます。

index.js
import { Provider, createStore } from 'react-redux'
const store = createStore()

ReactDOM.render(
  <Provider store={store}>
      <Main />
  </Provider>,
  document.getElementById('root')
);

Providerコンポーネントは、Reduxから提供されるstoreを配置するもので、アプリケーションのコンポーネントを内包するようにします。

Actions

データ操作の名前です。「Todoリストに追加」「買い物カゴから削除」などの操作をaddTodoListdeleteFromShoppingCartというように文字列で表現します。

actions.js
const increment = () => {
  return {
    type: ‘addTodoList’
  }
}
store.dispatch(increment())

Actionを実行させるには、Storeのdispatchメソッドにactionを指定します。 また、Actionの文字列を生成するメソッドをAction Creatorと呼びます。

Reducers

Storeの状態を変更するメソッドです。 Actionから直接Storeを変更することはできません。Storeを変更する際には必ずReducerを呼ぶ必要があります。

reducer.js
export default function Reducer(state, action) {
  switch (action.type) {
    case 'addTodoList': {
        return {
            ...state,
            todos: [
              ...state.todos,
              {
                task: action.payload,
              }
            ]
        }
    }
    default:
        return state
  }
}

createSliceによる書き方

最近のReduxの導入方法としてcreateSliceというのがあります。 前述のようにReduxで状態管理するためにはReducerやActionのファイルを作らなければなりません。 createSliceによって1つのファイルにReducer、Actionを定義することができ、見通しが良くなります。

createSliceを作成する

createSliceを使うために、次のコマンドを実行します。

console
npm i @reduxjs/toolkit
store.js
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './Components/Todo/todosSlice'

export const store = configureStore({
  reducer: {
    todos: todosReducer
  }
})

storeを作成、使用するReducerを登録します。この場合、storeのtodos配下でデータを管理します。

todosSlice.js
import { createSlice } from '@reduxjs/toolkit'

export const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    id: '',
    title: '',
    body: []
  },
  reducers: {
    addCard: (state, action) => {
      state.body.unshift({
        sort: "0", col1: action.payload.col1, col2: action.payload.col2
      })
      return state
    },
    changeCard: (state, action) => {
      const data = action.payload
      state.body[data.order][data.side] = data.value
      return state
    },
    deleteCard: (state, action) => {
      const order = action.payload
      state.body.splice(order, 1)
      return state
    },
  }
})

export const {
  addCard,
  changeCard,
  deleteCard,
  sortCard,
  changeTitle
} = cardsSlice.actions

export default cardsSlice.reducer

storeの初期状態と、Reducerを定義します。 Reducerは、状態(state)とパラメータ(action)受け取って新しい状態を返します。 ここで定義したReducerをexportすることで、他のコンポーネントから呼び出すことができます。

なお、上記コードにはActionが無いように見えますが、 createSliceがstore名/reducer名をキーとするActionを自動的に生成してくれています。

非同期処理

外部APIを呼ぶ非同期通信など、非同期処理を行う場合は、createAsyncThunkというメソッドを呼び出します。

todosSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

export const todosSlice = createSlice({
    ...(省略)
    extraReducers: {
    'todos/apiGetCards/fulfilled': (state, action) => {
      return {
        ...action.payload
      }
    }
  }
})

export const apiGetTodos = createAsyncThunk(
  'todos/apiGetTodos',
  async (id) => {
    const response = await axios.get(`https://www.example.com/todos/${id}`)
    return {
      id: response.data.id,
      title: response.data.title,
      body: JSON.parse(response.data.body)
    }
  }
)

createAsyncThunkの第一引数にアクション名を定義し、第二引数に非同期処理を記述します。

この非同期処理の状態を監視し、その状態に応じてstateを変更することができます。 状態にはfulfilled(成功), rejected(失敗), pending(待機中)の三種類があります。