🐉

Redux Toolkitをまとめてみた

2021/09/30に公開

React hooksも素晴らしいですが、様々な面で限界があるので状態管理ライブラリを導入した方がいいと思います。その中ではやっぱりまだReduxが最有力な選択肢でしょう。

今回はreduxと併用するRedux Toolkitについて勉強したので、今回まとめてみました。

この記事の概要

この記事は「Reduxの理解はあるが、Redux Toolkitと併用したことない」という人に向けて、Redux Toolkitを使うために必要な全体像の理解をざっとするというのが目的です。自分の勉強のログ残しも目的

redux toolkitのメリット

以下documentから引用したものです。

The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:

  • "Configuring a Redux store is too complicated"
  • "I have to add a lot of packages to get Redux to do anything useful"
  • "Redux requires too much boilerplate cod

ざっくり要約すると

  • Storeの理解を簡単にする
  • パッケージをたくさん入れずにすむようにする
  • 定型の繰り返されるコードを削減する

この3つがRedux Toolkitの目的と言えるでしょう。確かにActionに莫大なswitch文を書くのはとてもめんどくさいです。

These tools should be beneficial to all Redux users.

Reduxを使う人にとって、とても有益なpackageです。

install

今回は既存のアプリに適用させる場合のみ言及します。

// npm
npm install @reduxjs/toolkit react-redux
// yarn
yarn add @reduxjs/toolkit react-redux

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

実際の使い方

基本的にはsliceとasyncAction、そしてstoreを作成すればOKです。
ReduxではActionとReducerが分かれていたと思いますが、Toolkitを使いSliceで一元化することができます。

slice

このコードではユーザーのサインアウトを想定しています。単純にuserのstateを初期値に戻します。
createSliceを使うことでsliceとその内部でreducerを設定することができます。

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

export type User = {
  id: string | null
  username: string | null
  password: string | null
}

export type UserState = {
  user: User
}

export type UpdateUserPayload = User
export type AddHistoryPayload = string

export const initialState: UserState = {
  user: {
    id: null,
    username: null,
    password: null,
  },
}

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    signOut() {
      return initialState
    },
  },
})

export default userSlice

https://redux-toolkit.js.org/api/createSlice

store

先ほど作成したuserSliceのreducerをstoreのreducerに設定してください。
加えてsliceの数が複数になった際はcombineReducersで合体させた方が良いです。

import { Store } from 'redux';
import logger from 'redux-logger';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import userSlice, { initialState as userState } from './users/slice';

const preloadedState = () => {
  return userState;
};

export type StoreState = ReturnType<typeof preloadedState>;

export type ReduxStore = Store<StoreState>;

const middlewareList = [...getDefaultMiddleware(), logger];
const createStore = configureStore({
  reducer: userSlice.reducer,
  middleware: middlewareList,
  preloadedState: preloadedState(),
  devTools: true,
});


export default createStore;

middlewareなどの必要なものは各自で設定して下さい。

https://redux-toolkit.js.org/api/configureStore

createAsyncThunk

createAsyncThunkは非同期処理のreducerと捉えてください。先ほどのsliceに追記します。
また、通信のステータス(pending,fulfilled,rejected)ごとに処理を条件分岐して記述することもできます。

import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { signIn,signUp } from './asyncAction'

export type User = {
  id?: string | null
  username: string | null
  password: string | null
}

export type UserState = {
  user: User
}

export type UpdateUserPayload = User
export type AddHistoryPayload = string

export const initialState: UserState = {
  user: {
    id: null,
    username: null,
    password: null,
  },
}

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    signOut() {
      return initialState
    },
  },
  // 追記------------------------------------
  extraReducers: (builder) => {
    // signUp
    builder.addCase(signUp.rejected,(state, action) => {
      console.log(action.error.message);
    });
    builder.addCase(signUp.fulfilled, (state, action: PayloadAction<UserState>) => {
      state.user = {
        id: action.payload.user.id,
        username: action.payload.user.username,
        password: action.payload.user.password
      }
    });
    // signIn
    builder.addCase(signIn.rejected,(state, action) => {
      console.log(action.error.message);
    });
    builder.addCase(signIn.fulfilled, (state, action: PayloadAction<UserState>) => {
      state.user = {
        id: action.payload.user.id,
        username: action.payload.user.username,
        password: action.payload.user.password
      }
    })
  }
  // ここまで------------------------------------
})

export default userSlice

asyncActionファイルを作成して、そこに処理を記述しました。

今回はサッと試すためにrails APIモードをバックエンドにしてjsonでデータをフェッチしてくるようにしました。とりあえずRedux Toolkitの挙動確認のためにサッとやったので細かい部分のご指摘は遠慮ください。

import { createAsyncThunk } from '@reduxjs/toolkit';
import { UserState } from './slice';

export const signIn = createAsyncThunk(
  'users/signIn',
  async (arg: UserState): Promise<UserState> => {
    const response = await fetch(
      "http://localhost:3000/users/signIn", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(arg)
    });
    const json = await response.json();
    // reducerへ
    return {
      user: json
    };
  }
);

export const signUp = createAsyncThunk(
  'users/signUp',
  async (arg: UserState): Promise<UserState> => {
    const response = await fetch(
      "http://localhost:3000/users/signUp", {
      method: "POST",
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(arg)
    });
    const json = await response.json();
    // reducerへ
    return {
      user: json
    };
  }
);

https://redux-toolkit.js.org/api/createAsyncThunk

コンポーネントから呼び出すには

画面側では関数を定義してあげて、dispatchで呼び出せばOK.

const onclickSignIn = (params: UserState) => {
    dispatch(signIn(params));
}

単純に値を使いたい時もuseSelectorを使えばOK.

const user = useSelector((state: UserState) => state.user);

Storeをラッピングしてあげよう。

もちろん状態をコンポーネント間で管理できるようにして下さい。
Provider と createStoreをimportした上で子コンポーネントに渡せばOK.

import { Provider } from 'react-redux';
import createStore from '../redux/createStore';
-----------------------------------------------

<Provider store={createStore}>
  <Component {...pageProps} />
</Provider>

参考にしたサイト

https://redux-toolkit.js.org/

https://zenn.dev/mayamashita/articles/12ab4a122a02cc

参考になれば幸いです。

GitHubで編集を提案

Discussion