1. DX支援サービス

    進化したデジタル技術を浸透させることで人々の生活をより良いものへと変革する

  2. ソフトウェア開発サービス

    VAREALだからできる、RubyとRuby on Railsに特化した、素早く柔軟なソフトウェア開発

  3. AI関連サービス

    データ活用と機械学習を用いたビジネスの着実な深化を。

  4. クリエイティブサービス事業

    美しいだけではない
    機能的UI/UXと正しいコーディング。

  1. SUNWEALC株式会社様 資産形成アプリ「enrich」のMVP開発支援

  2. 株式会社みらいワークス様 求人サイト「MOREWORKS」のメンテナンス支援

  3. 株式会社エムステージ様 Ruby/Railsのバージョンアップ支援

  4. 株式会社アイキューブドシステムズ様 CLOMO MDMのリプレイス開発支援

  5. ライオン株式会社様 「by me」のAI診断サービスの開発支援

  6. 株式会社TOEZ様 幼児向けのレッスン通信講座サイトおよび基幹システムの開発支援

  7. 株式会社カカクコム様 食べログノート の開発支援

  8. 有限会社秀栄社様 パーソナライズ絵本「JibunEHON」の開発支援

  9. 株式会社TRN様 不動産会社・建築会社向け_営業支援システム「renovo」の開発支援

  10. 株式会社Touch&Links様 新規CMSのシステム構築

  11. オフショア開発・長期ラボ型 Webアプリケーション開発事例/顧客ロイヤリティを高めるサービスの開発(株式会社ギフティ様)

  12. イベントサイト 「オンラインで集まろう 学研クリスマス&おとしだまウィーク」

  1. 株式会社ワイドウィンドウズ様 予約管理システムの既存機能の精査・変更と新規機能の追加およびUI/UXデザイン支援

  2. ライオン株式会社様 「by me」のAI診断サービスの開発支援

  3. 製造業のDX支援〜営業日報管理システム開発〜

  4. ウォータージェット加工.com サイトリニューアル

  5. 佳秀バイオケムサイトリニューアル

  6. 佳秀工業株式会社コーポレートサイトリニューアル

  7. 開発コンサルティング

  8. 団体管理システム

  9. ITコンサルティング

  1. 株式会社アイキューブドシステムズ様 CLOMO MDMのリプレイス開発支援

  2. 株式会社マネーフォワード様 マネーフォワード クラウドの開発支援

  3. 株式会社フレンバシー様 ベジタリアン、ヴィーガン向けのレストラン検索サイトの開発

  4. ライオン株式会社様 「by me」のAI診断サービスの開発支援

  5. 株式会社カカクコム様 食べログノート の開発支援

  6. 大手建設コンサルティング会社I社様 「自然災害を検知するAI」の開発 

  7. Webサイト訪問者分析のためのデータ分析基盤構築

  8. 製造業のDX支援〜営業日報管理システム開発〜

  9. 生産管理システム

  10. 仮想化サーバー導入

  11. タブレット端末導入

  1. VAREAL AI HUBの開発

  2. ライオン株式会社様 「by me」のAI診断サービスの開発支援

  3. 北海道大学様 オープンソースの大規模言語モデル(LLM)を使用したプロダクト共同研究開発

  4. 埼玉医科大学様 画像分類AIを用いた膠原病診断補助ツールの研究開発

  5. 大手建設コンサルティング会社I社様 「自然災害を検知するAI」の開発 

  1. ライオン株式会社様 「by me」のAI診断サービスの開発支援

  2. イベントサイト 「オンラインで集まろう 学研クリスマス&おとしだまウィーク」

  3. ウォータージェット加工.com サイトリニューアル

  4. 佳秀バイオケムサイトリニューアル

  5. スカイライト コンサルティング様コーポレイトサイトリニューアル

  6. ハイブリィド株式会社 様 [ IT-Manager SD ]

  7. 国際的機関の組織内システム開発

  8. 既存会計サービスのUI/UXデザイン改善

  9. 株式会社 クリニカル・トライアル 様 希少疾患SNS「RareS.(レアズ)」

  10. 人材マネジメントシステムUI/UXデザイン

  11. 保育園関連情報メディア開発

  12. Vareal株式会社中途採用情報サイト

Development

React Reduxの仕組みを解説


実務でReduxを使用するプロジェクトに入る予定なので、Reduxの学習をしました。
本ブログでは、より理解を深めるためにReact+ReduxでのバックエンドとのAPI通信を用いてReduxの仕組みを解説いたします。

ブログ作成者紹介

氏名:D・H
所属:開発部
入社年:2024年4月

解説の流れ

  1. Reduxの利点の説明
  2. ログイン機能を用いてreduxの処理の流れの説明
  3. ReduxToolkitを使用した際のコードの変化の説明
  4. ログイン状態を維持させる(おまけ)

    1.Reduxの利点の説明

    Reduxは状態管理のライブラリです。
    アプリケーションの状態を一元管理することで、予測可能な方法で状態の更新を行うことができます。
    私が考える主な利点は二つあります。

状態が一元管理されるのでpropsの受け渡しをせずに済みます

コンポーネントの階層構造が深ければ深いほどメリットがあると思います。

reduxの状態管理の流れを理解すれば状態の流れを追いやすい

・action
状態変化を表すオブジェクトです。アクションは、何かが起こったことを示し、その結果として状態が変化することを伝えます。
・dispatch
アクションをストア内のリデューサーに渡す機能です。
ディスパッチは、アクションを送信し、リデューサーがそれを処理して新しい状態を生成するプロセスをトリガーします。
・store
状態を保持するオブジェクトです。
Reducerを使って状態の変化を処理します。
Reducerは、現在の状態とアクションを受け取って新しい状態に上書きする純粋な関数です。

2. ログイン機能を用いてreduxの処理の流れの説明

ここからはコードを用いてReduxの処理の流れを説明します。

以下の流れで状態変化が起こります
ログインボタンを押す

アクションクリエーターをdispatchする

reducerがアクションを受け取り、状態を更新する

dispatch
dispatchはreducerにactionの内容を通知します。
今回は以下のactionの情報をdispatchを用いてstore内のreducerに伝えます。
・ログインのステータスがtrueになる
・ログインしたユーザーの情報が入る(backendから取得してきた情報)

ボタンを押すとuseLoginAuthActionが実行されます。

frontend/src/hooks/users/useLoginAuthAction.tsx

import { useDispatch } from 'react-redux'
import { setLoginStatus, setCurrentUser } from 'actions/sessionActions'
import { SignInParams, User } from 'types/users/session'
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import { client } from 'lib/api/client';

export const useLoginAuthAction = (signInParams: SignInParams) => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [errorMessages, setErrorMessages] = useState<String[]>([]);

  const afterLoginSuccess = (data: User) => {
    dispatch(setLoginStatus(true));
    dispatch(setCurrentUser(data));
    data.admin === false?
    navigate(`/users/${data.id}`, {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}})
    :
    navigate('/', {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}});
  }

  const loginAction: React.MouseEventHandler<HTMLButtonElement> = (e) => {
    client.post('login', { signInParams })
      .then(response => {
        afterLoginSuccess(response.data);
      })
      .catch(error => {
        if (error.response && error.response.status === 401) {
          setErrorMessages(error.response.data.errorMessages);
        } else {
          setErrorMessages(['予期しないエラーが発生しました']);
        }
        navigate('', { state: { message: 'ログインに失敗しました', type: 'error-message' } });
      }
    );
  }

  return { loginAction, errorMessages}
}

上のコードから大事なのはafterLoginSuccess関数のdispatchの内容です。

 const afterLoginSuccess = (data: User) => {
    dispatch(setLoginStatus(true));
    dispatch(setCurrentUser(data));
    data.admin === false?
    navigate(`/users/${data.id}`, {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}})
    :
    navigate('/', {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}});
  }
  • dispatch(setLoginStatus(true)):
    • setLoginStatusアクションクリエーターが呼び出され、{ type: SET_LOGIN_STATUS, payload: true }というアクションオブジェクトが生成されます。
    • このアクションオブジェクトがdispatch関数に渡され、Reduxのストアに送信されます。
  • dispatch(setCurrentUser(data)):
    • setCurrentUserアクションクリエーターが呼び出され、{ type: SET_CURRENT_USER, payload: data }というアクションオブジェクトが生成されます。
    • dataの内容はbackendから取得してきたuser情報です。
    • このアクションオブジェクトもdispatch関数に渡され、Reduxのストアに送信されます。

action
こちらは上のdispatchのコードで使用しているactionの内容です。

import { UserResponseData } from "types/users/response";

export const SET_LOGIN_STATUS = 'SET_LOGIN_STATUS';
export const SET_CURRENT_USER = 'SET_CURRENT_USER';

export const setLoginStatus = (status: boolean) => ({
  type: SET_LOGIN_STATUS,
  payload: status
});

export const setCurrentUser = (user: UserResponseData | {}) => ({
  type: SET_CURRENT_USER,
  payload: user
});

こちらのタイプからreducerがどの状態を上書きするか判断します。
payloadの部分には上書きしたいデータの内容が入ります。
例えば、setLoginStatusにはstatusが入り、boolean型のデータが入ります。

export const setLoginStatus = (status: boolean) => ({
  type: SET_LOGIN_STATUS,
  payload: status
});

Store
disptachされたアクションは下のコードのstoreに渡されます。
rootReducerは、combineReducersを使って複数のリデューサーをまとめたものです。
この場合、sessionというキーに対してloginReducerが割り当てられています。
rootReducer内のreducerすべてにactionを渡します。

import { combineReducers, createStore } from "redux";
import loginReducer from "./loginReducer";

const rootReducer = combineReducers({
  session: loginReducer
});

const store = createStore(rootReducer)

export type RootState = ReturnType<typeof rootReducer>
export default store;

Reducer
frontend/src/reducers/loginReducer.ts

import { initialLoginState } from 'defaults/userDefaults';
import { SET_LOGIN_STATUS, SET_CURRENT_USER } from '../actions/sessionActions';
import { User } from "types/users/session";

type LoginAction = {
  type: typeof SET_LOGIN_STATUS;
  payload: boolean;
}

type CurrentUserAction = {
  type: typeof SET_CURRENT_USER;
  payload: User;
}

type Action = LoginAction | CurrentUserAction;

const loginReducer = (state = initialLoginState, action: Action) => {
  switch (action.type) {
    case SET_LOGIN_STATUS:
      return {
        ...state,
        loginStatus: action.payload
      };
    case SET_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload
      };
    default:
      return state;
  }
};

export default loginReducer;

・loginReducerは、渡されたアクションのtypeに基づいて状態を更新します。
・SET_LOGIN_STATUSの場合、state.loginStatusがアクションのpayload(この場合はtrue)に更新されます。
・SET_CURRENT_USERの場合、state.currentUserがアクションのpayload(この場合はdata.user)に更新されます。

渡されたactionタイプの中から該当のものがあれば、現在のstateからpayloadの内容に上書きします。
なければ、以前の状態のstateを渡します。
上記の流れで状態管理の更新が行われます。

3. ReduxToolkitを使用した際のコードの変化の説明

実は上のReduxのコードは現在推奨されている実装方法ではありません。

ReduxToolkitを使用した方法が推奨されているやり方です。
ただ、reduxの処理の流れを理解していた方がReduxToolkitを使用する際に理解が深まるので説明しました。
ReduxToolkitを使用したら上のコードがどれだけ省略できるかも注目していただきたいです。

ReduxToolkitをインストールします。

yarn add @reduxjs/toolkit

ReducerからSliceに変更する

ファイル名をloginReducerからloginSliceに変更後、処理を変更する。

変更前
frontend/src/reducers/loginReducer.ts

import { initialLoginState } from 'defaults/userDefaults';
import { SET_LOGIN_STATUS, SET_CURRENT_USER } from '../actions/sessionActions';
import { User } from "types/users/session";

type LoginAction = {
  type: typeof SET_LOGIN_STATUS;
  payload: boolean;
}

type CurrentUserAction = {
  type: typeof SET_CURRENT_USER;
  payload: User;
}

type Action = LoginAction | CurrentUserAction;

const loginReducer = (state = initialLoginState, action: Action) => {
  switch (action.type) {
    case SET_LOGIN_STATUS:
      return {
        ...state,
        loginStatus: action.payload
      };
    case SET_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload
      };
    default:
      return state;
  }
};

export default loginReducer;

変更後
frontend/src/reducers/loginSlice

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { initialLoginState} from "defaults/userDefaults";
import { User } from "types/users/session";

const loginSlice = createSlice({
  name: 'login',
  initialState: initialLoginState,
  reducers: {
    setLoginStatus(state, action: PayloadAction<boolean>) {
      state.loginStatus = action.payload;
    },
    setCurrentUser(state, action: PayloadAction<User>) {
      state.currentUser = action.payload;
    }
  }
});

export const { setLoginStatus, setCurrentUser} = loginSlice.actions;
export default loginSlice.reducer;

変更後のコードの解説をします。
createSliceを使用することでactionを定義する必要がなくなります。
setLoginStatusとsetCurrentUserがactionのアクションクリエータの役割を示しています。

dispatch
dispatchに渡すアクションクリエータもloginReducerからimportするものに変更します。
これによりactionファイルは削除できます。

import { useDispatch } from 'react-redux';
import { setLoginStatus, setCurrentUser } from 'reducers/loginSlice';

export const useLoginAuthAction = (signInParams: SignInParams) => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [errorMessages, setErrorMessages] = useState<String[]>([]);

  const afterLoginSuccess = (data: User) => {
    dispatch(setLoginStatus(true));
    dispatch(setCurrentUser(data));
    data.admin === false?
    navigate(`/users/${data.id}`, {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}})
    :
    navigate('/', {state: {message: 'ログインに成功しました', type: 'success-message', condition: true}});
  }

configureStoreを使用してストアを設定

ReduxToolkitのコードに変更する。

変更前

import { combineReducers, createStore } from "redux";
import loginReducer from "./loginReducer";

const rootReducer = combineReducers({
  session: loginReducer
});

const store = createStore(rootReducer)

export type RootState = ReturnType<typeof rootReducer>
export default store;

変更後

import { configureStore } from '@reduxjs/toolkit';
import loginReducer from './loginSlice';

const store = configureStore({
  reducer: {
    session: loginReducer
  }
})

export type RootState = ReturnType<typeof store.getState>;
export default store;

ReduxToolkitのconfigureStoreを使用することで、createStoreとcombineReducersを手動で設定する必要がなくなります。
configureStoreは、デフォルトでいくつかの便利なミドルウェア(redux-thunk)を含んでおり、開発者体験を向上させます。

4. ログイン状態を維持させる(おまけ)

ローカルストレージに保存して、ログイン状態を維持できるようにします。
そうすることでページを更新してもログイン状態を維持できるようにします。
Reduxではredux-persistを使用することでログイン状態を維持できるようにできます。

import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import loginReducer from './loginSlice';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['session']
};

const rootReducer = combineReducers({
  session: loginReducer
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({
    serializableCheck: {
      ignoredActions: ['persist/PERSIST']
    }
  })
});

export type RootState = ReturnType<typeof store.getState>;
export const persistor = persistStore(store);
export default store;

おわりに

本ブログでは、Reduxのメカニズムを例を用いて解説しました。
ReduxToolkitを使用する際も、Reduxのメカニズムが必要なので勉強になりました。
本ブログがReduxへの理解に繋がれば幸いです。

参考

https://redux.js.org/

わたしたちと一緒に働いていただける方を大募集しています!
興味のある方はぜひ下記リンクをチェックしてみてください!

X(旧Twitter)では社内の雰囲気を発信しています、ぜひフォローしてみてください!

関連記事

%d人のブロガーが「いいね」をつけました。