Redux ToolKit概要

はじめに

*この記事は、あくまで現時点で知っている情報をまとめているに過ぎないので、参考程度に留めておいてください。

Redux Toolkitとは

Reduxの開発チームが効率的で快適なDXを目指して開発されたライブラリです。
Reduxは、最小限の機能しか持っていないため、導入するためには、役割ごとに各モジュールを自身で準備しないといけません。

それは、たとえ、Reduxに習熟したとしても、必要になる定型文のようなコードをいちいち書かなければいけない事を意味します。また、アプリの規模が大きくなってくると、様々な種類のデータをどのように持つか、Storeの分割をどのようにするかということも、各自、各チームで判断しないといけなくなります。(いくつかのパターンが提案されているのみ)

上記の問題を解決する上で、Redux Toolkitを導入することは、生産性の向上に繋がるとして、注目されているように感じます。

主要なAPI

主要なAPIは以下の通りです。
各モジュールの生成やStoreの分割方法をヘルパー関数が用意されており、Reduxの導入を手助けしてくれます。

configureStore
各種デフォルト値が設定可能なcreateStateのカスタム値

createReducer
reducerの作成を簡単にしてくれる
(SliceのReducerを結合する際に使うケースが多いかも)

createAction
action creatorを作成する
(createSliceを使うので、使うケースは少ないかも)

createSlice
actionの定義とaction creator, reducerをまとめて生成できる

Redux Toolkitを導入して書き直してみる

TypeScript導入していないシンプルな例で、Redux Toolkitを導入した書き換えをしてみます。 導入時のコマンドは以下の通りです。

npm install --save @reduxjs/toolkit react-redux

【Action】

Redux Toolkit導入前

export const increment = () => ({ 
  type: "INCREMENT" 
})

export const decrement = () => ({
  type: "DECREMENT"
})

Redux Toolkitで書き直したもの

import { createAction } from '@reduxjs/toolkit';

const FEATURE = 'counter';
export const incremented = createAction(`${FEATURE}/increment`);
export const decremented = createAction(`${FEATURE}/decrement`);

【Reducer】

Redux Toolkit導入前

export const initialState = { count: 0 };

export const countReducer = (state = initialState, action) => {
  switch(action.type) {
    case 'INCREMENT' :
      return {
        ...state, count: state.count + 1
      };
    case 'DECREMENT':
      return {
        ...state, count: state.count -1
      }
    default: {
      return state;
    }
  }
}

Redux Toolkitで書き直したものです。 Switch文を書かずに、すっきり書けます。

import { createReducer } from '@reduxjs/toolkit';
import { incremented, decremented } from './counter-action';

export type CounterState = {
  count: number;
}

const initialState: CounterState = { count: 0 };

export const counterReducer = createReducer(initialState, {
  [incremented.type]: (state) => ({...state, count: state.count + 1}),
  [decremented.type]: (state) => ({...state, count: state.count - 1}),
})

上記を__createSlice__を使ってさらに短く書くと、以下のようになります。
actionとreducerのロジックを統合したものを、__Slice__と呼びます。

構成にもよるかと思いますが、Sliceとは、__Store全体を構成する一部分のStore__を意味します。

例えば、ECサイトなどで考えると、User、Item、Cartなど、状態の内容や用途別に、State、Action Creator、Reducerを切り分けてまとめることで、コードの見通しを良くしてくれます。

createSlice関数は、State、Action Creator、Reducerをまとめて作成することが出来る関数です。即ち、上記で触れたActionsとReducerは、createSlice関数で生成することが可能です。

import { createSlice } from '@reduxjs/toolkit';

export type CounterState = {
  count: number;
}

const initialState: CounterState = { count: 0 };

export const counterSlice = createSlice({
  name: 'counter', // Sliceの名前
  initialState, // 初期State
  reducers: { // Reducer(=StoreのStateに対して更新処理を定義する役割)
   // 以下のKeyがAction Creatorの関数名=Action Creatorが生成される。
   // 以下コードでは第一引数に現在のStateのみ渡してますが、第二引数は渡されたactionを記述
    incremented: (state) => ({ ...state, count: state.count + 1 }),
    decremented: (state) => ({ ...state, count: state.count - 1 }),
  }
})

上記によって作成されたSliceは、以下のプロパティを持つオブジェクトとなります。

const hogeSlice = createSlice(/* ... */);

hogeSlice.name;
hogeSlice.reducer;
hogeSlice.actions;

【役割ごとに生成したReducerを結合してStoreを生成】

例えば、上記で触れたECサイトを開発する際に、User、Item、Cartなど、それぞれcreateSliceで作成したReducerは一つにまとめた上で、Storeを生成しなければなりません。その場合は、以下のような記述になるかと思います。

import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";

// createSliceで生成したReducer
import userReducer from "./user";
import itemReducer from "./item";
import cartReducer from "./cart";

// combineReducers関数でReducerを結合
const reducer = combineReducers({
  user: userReducer,
  item: itemReducer,
  cart: cartReducer
});

// configureStoreに結合したReducerを渡して、Storeを生成
const store = configureStore({ reducer });

export default store;

【トップレベル】

Redux Toolkit導入前

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { initialState, reducer } from './Reducer/reducer';

const store = createStore(reducer, initialState);

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

Redux Toolkitで書き直したもの createSlice関数で生成されたReducerを渡してます。

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import store from './store'; // 結合したReducerが渡されているStore

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

コンポーネント

Redux Toolkit導入前

import React from 'react';
import { increment, decrement } from 'Action/actionCreator';
import { useSelector, useDispatch } from 'react-redux';

function App() {

  const count = useSelector(state => state.count);
  const dispatch = useDispatch()

  return(
    <>
    <div>{count}</div>
    <button onClick={()=> dispatch(increment())}>click</button>
    <button onClick={()=> dispatch(decrement())}>click</button>
    </>
  )
}

export default App;

Redux Toolkitで書き直したもの

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { counterSlice } from 'features/counter/counter';

function App() {

  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  const { incremented, decremented } = counterSlice.actions;

  return(
    <>
    <div>{count}</div>
    <button onClick={()=> dispatch(incremented())}>click</button>
    <button onClick={()=> dispatch(decremented())}>click</button>
    </>
  )
}

export default App;

まとめ

以上、簡単にですが、Redux Toolkitの紹介でした。 これは、沢山書いて、慣れるしかなさそう。。。