💦

ReduxとRedux ToolKitの比較

2021/03/29に公開

今さらですがRedux ToolKitを初めて触ったので、Reduxと比較しながら感想を載せたいと思います。

Redux ToolKitとは

Reduxをより簡潔に使用できるツールキットのことです。
ツールキットというだけあって、reduxのメモ化に必要なreselectや非同期処理を担うthunkなど、当初別々に分かれていたライブラリが1つにまとまった便利なキットとも捉えています。

Reduxのおさらい

Reduxは主に下記要素から構成されていました。

1. store(state)
2. reducer
3. action creator
4. middleware

それぞれ定義するファイルが異なっており、action-creatorを定義して、別ファイルであるreducerにactionを読み込ませて、別ファイルでそれらをcombineReducersで統合して…のようにファイルを何度も関連付ける必要がありました。

またreducerといってもモジュール分割したreducerや、それらを統合したreducerであるroot-reducerなどがあり、同じファイルの中でも設定しないといけないことが多いと感じていました。

Reduxでactionを1から定義/使用するとき

1. ActionCreatorを定義する
2. reducerにactionを登録する(switch文を使う)
3. 各reducerをcombineReducersでrootReducerに統合する
4. storeにrootReducerを読み込ませ、必要ならmiddlewareを登録
5. Appにprovideして、jsx内でactionを読み込ませる

Reduxで必要だったファイル/設定

1. ActionCreator
2. reducers(UserReducer, CartReducerなど)
3. RootReducer
4. selectors(reselect)
5. store.ts(applyMiddleware, createStore, persistStore)
6. index.tsx(Provider, PersistGate)

action-creator

// user.action.ts
import { CurrentUser, SET_CURRENT_USER, setCurrentUserAction } from '../../types'

export const setCurrentUser = (user: CurrentUser): setCurrentUserAction => ({
  type: SET_CURRENT_USER,
  payload: user
})

reducer(module)

// user.reducer.ts
import { UserState, SET_CURRENT_USER, UserActionTypes } from '../../types'

const INITIAL_STATE: UserState = {
  currentUser: null
}

const userReducer = (state = INITIAL_STATE, action: UserActionTypes): UserState => {
  switch (action.type) {
    case SET_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload
      }
    default:
      return state
  }
}

export default userReducer

selector(reselect)

// user.selectors.ts
import { createSelector } from 'reselect'
import { UserState, RootState } from '../../types'

const selectUser = (state: RootState): UserState => state.user

export const selectCurrentUser = createSelector(
  selectUser,
  user => user.currentUser
)

root-reducer

// root-reducer.ts
import userReducer from './user/user.reducer'
import cartReducer from './cart/cart.reducer'
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
  user: userReducer,
  cart: cartReducer
})

store(createStore, persistStore)

// store.ts
import { createStore, applyMiddleware } from 'redux'
import { persistStore } from 'redux-persist'
import logger from 'redux-logger'
import rootReducer from './root-reducer'
import thunk from 'redux-thunk'

type Middlewares = (typeof thunk | typeof logger)[]

const middlewares: Middlewares = [thunk]

if (process.env.NODE_ENV === 'development') {
  middlewares.push(logger)
}

export const store = createStore(rootReducer, applyMiddleware(...middlewares))
export const persistor = persistStore(store)

index.tsx(Provider, PersistGate)

// index.tsx
import { store, persistor } from './redux/store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'

<Provider store={store}>
  <PersistGate persistor={persistor}>
      <App />
  </PersistGate>
</Provider>

Redux ToolKitの場合

これがRedux ToolKitを使うとどうなるのかを簡単に説明します。

Reduxとの比較

ToolKitでは、Reduxでモジュールとして管理していた各reducerはsliceという単位で管理されます。

// Reduxのroot-reducer.ts
import userReducer from './user/user.reducer'
import cartReducer from './cart/cart.reducer'
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
  user: userReducer, // Redux ToolKitではuserSliceとして管理される
  cart: cartReducer  // cartSliceとして管理される
})

sliceの定義

するとsliceが管理の最小単位となるので、sliceごとにstore(state), reducer, actionを管理することができるようになります。

// userSlice.ts
import { createSlice } from "@reduxjs/toolkit";

// 1. InitialStateを定義
// Reduxではuser.reducer.tsに定義していた
const initialState = {
  user: {
    uid: "",
    email: "",
    photoURL: "",
    displayName: "",
  },
};

// 2. ReducerおよびActionCreatorを定義
// Reduxではuser.reducer.tsに定義していた
// reducer特有のswitch文を書かなくて良い
export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    login: (state, action) => {
      state.user = action.payload;
    },
  },
});

export const { login } = userSlice.actions;

// 3. selector(reselect)を定義
// Reduxではuser.selector.tsに定義していた

export const selectUser = ({ user }) => user.user;

export default userSlice.reducer;

sliceの読み込み

上記sliceを作成したあとは各sliceをstore.tsで読み込むだけです。

// store.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import userSlice from "../features/userSlice";

export const store = configureStore({
  reducer: {
    user: userSlice, // ここで読み込む
    ...
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

index.tsxでProviderを設定するなどは、create-react-app時にRedux ToolKitが予め行ってくれているので不要です。

このsliceのおかげで、action, reducerごとに複数のファイルを作成することもなくなりますし、どこに何を定義するのか意識する必要もなくなります。

まとめ

以上、Redux ToolKitを使ってみた感想となります。
Reduxのあの設定から抜け出せると思うととても気が楽になります。。
reselectやthunkなども細かい設定不要で使えるので、個人開発程度のプロダクトでも導入しておいていいのでは?と思いました。


参考

ReduxToolKit - Getting Started
https://redux-toolkit.js.org/introduction/getting-started

Discussion