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導入前
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-thunk
やredux-devtools-extension
を導入するために色々やっているのがわかると思います。
after
Redux Toolkit導入後
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
configureStore
にredux-thunk
やredux-devtools-extension
が含まれているので、だいぶコードがスッキリしました。
Action
これまではTypescriptの型補完を効かせるために、typescript-fsa
のactionCreatorを使用してactionを定義していましたが、Redux Toolkitでも同様のことが可能です。
before
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
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
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
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というこれまでにはなかった新機能が存在します。
Sliceはaction
とreducer
をひとまとめにしたもので、Redux ToolkitのcreateSlice
を使うことで、reducer
とaction
の一括定義が可能になります。
これまで例として紹介してきたActionとReducerをcreateSlice
を使って定義する場合は以下の通りになります。
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
これ以上ないくらいシンプルになりました 😓
使い方
これまでと同様にaction
をdispatch
するだけです
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>
)
}
※ 上のuseDispatch
やuseSelector
にも型補完を効かせたい場合は以下のようなuseAppDispatch
やuseAppSelector
を定義して代わりに使用してください。
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
おわり
昔は導入大変だった... 👴