thumbnail

Redux ToolkitでRedux導入がすごく楽になった件

2021/10/16

ReactでWeb App開発をしているとほぼ確実にお世話になるReduxですが、導入が面倒なのとコードの記述が冗長になりやすいのが玉に瑕でした。 そういう声が多かったのか、Redux公式がRedux補助用に新たに開発したライブラリがRedux Toolkitです。 Redux Toolkitはその名の通りReduxの導入や開発をより簡潔にしてくれるツールセットです。 どれくらいReduxの開発が簡潔になったのかを説明するために、今回はRedux Toolkit導入前と導入後でReduxまわりのコードがどれだけ変化したかを見ていきます。

必要パッケージ

まずRedux ToolkitのおかげでこれまでRedux開発に必要だったパッケージが大幅に減りました。 これだけでRedux Toolkitを採用する価値があると思います 😏

before

  • redux
    redux本体
  • react-redux
    reactとreduxをつなげるライブラリ
  • redux-thunk
    reduxで非同期処理を行うためのミドルウェア
  • typescript-fsa
    reduxのactionで型補完を行うためのライブラリ
  • typescript-fsa-reducers
    reduxのreducerで型補完を行うためのライブラリ
  • redux-devtools-extension
    開発環境でreduxの開発を補助してくれるライブラリ

after

redux-toolkitを使用する場合に必要なライブラリは以下の2つだけです。

  • react-redux
  • @reduxjs/toolkit

beforeで紹介したreact-reduxを除くライブラリの機能がすべて@reduxjs/toolkitに含まれています😮

Store

storeを作成する部分もかなり簡潔になりました。

before

Redux Toolkit導入前

createStore.ts
import {
  combineReducers,
  createStore as reduxCreateStore,
  Store,
  applyMiddleware,
  compose,
} from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import axios from 'axios'

const client = axios.create({ baseURL: 'https://api.example' })
const thunkWithClient = thunk.withExtraArgument(client)

const reducer = combineReducers({
  /*reducers*/
})

const composeEnhancer: typeof composeWithDevTools =
  process.env.NODE_ENV === 'development' ? composeWithDevTools : compose

const initialData = {}
const createStore = (): Store =>
  reduxCreateStore(
    reducer,
    initialData,
    composeEnhancer(applyMiddleware(thunkWithClient))
  )

export default createStore

redux-thunkredux-devtools-extensionを導入するために色々やっているのがわかると思います。

after

Redux Toolkit導入後

createStore
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  reducer: { /*reducers*/},
})

/* typescriptを使用する場合は↓の型定義をexportする */
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

configureStoreredux-thunkredux-devtools-extensionが含まれているので、だいぶコードがスッキリしました。

Action

これまではTypescriptの型補完を効かせるために、typescript-fsaのactionCreatorを使用してactionを定義していましたが、Redux Toolkitでも同様のことが可能です。

before

action.ts
import actionCreatorFactory from 'typescript-fsa'

const actionCreator = actionCreatorFactory()
const increment = actionCreator<number>('counter/increment')

const action = increment(3)
console.log(action)
// returns { type: 'counter/increment', payload: 3 }

after

action.ts
import { createAction } from '@reduxjs/toolkit'

const increment = createAction<number>('counter/increment')

const action = increment(3)
console.log(action)
// returns { type: 'counter/increment', payload: 3 }

Reducer

reducerも同様にtypescriptの型補完が可能になっています。(typescript-fsa-reducersと同じ機能)

before

reducer.ts
import actionCreatorFactory from 'typescript-fsa'
import { reducerWithInitialState } from 'typescript-fsa-reducers'

type CounterState = {
  value: number
}

const actionCreator = actionCreatorFactory()
const increment = actionCreator<number>('counter/increment')

const initialState: CounterState = { value: 0 }

const counterReducer = reducerWithInitialState(initialState)
  .case(increment, (state, payload) => {
    state.value = state.value + payload
  }

after

reducer.ts
import { createAction, createReducer } from '@reduxjs/toolkit'

type CounterState = {
  value: number
}

const increment = createAction<number>('counter/increment')

const initialState: CounterState = { value: 0 }

const counterReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state, action) => {
      state.value += action.payload
    })
})

Slice

Redux ToolkitにはさらにSliceというこれまでにはなかった新機能が存在します。 Sliceactionreducerをひとまとめにしたもので、Redux ToolkitのcreateSliceを使うことで、reduceractionの一括定義が可能になります。

これまで例として紹介してきたActionReducercreateSliceを使って定義する場合は以下の通りになります。

createSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

type CounterState = {
  value: number
}

const initialState: CounterState = { value: 0 }

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state, action: PayloadAction<number>) {
      state.value += action.payload
    },
  },
})

export const { increment } = counterSlice.actions
export default counterSlice.reducer

これ以上ないくらいシンプルになりました 😓

使い方

これまでと同様にactiondispatchするだけです

example.tsx
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { increment } from './slice'

const Example: React.FC = () => {
  const dispatch = useDispatch()
  const counter = useSelector((state) => state.counter)

  return (
    <div>
      <div>{counter.value}</div>
      <button onClick={() => dispatch(increment(1))}>ADD</button>
    </div>
  )
}

※ 上のuseDispatchuseSelectorにも型補完を効かせたい場合は以下のようなuseAppDispatchuseAppSelectorを定義して代わりに使用してください。

reduxHooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from '../redux/store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

おわり

昔は導入大変だった... 👴

author picture

Mitsuru Takahashi

京都市内にてフリーランスエンジニアとして活動しています。

detail

Profile

author picture

Mitsuru Takahashi

京都市内にてフリーランスエンジニアとして活動しています。

detail

© 2022 mitsuru takahashi