LoginSignup
4
0

More than 1 year has passed since last update.

Reduxで非同期処理を扱う方法(Redux Thunk)

Last updated at Posted at 2023-03-28

◆目次

【 Redux-Thunkについて 】

・はじめに

・Redux Thunkを用いた非同期処理の流れ

・Redux Thunkの定義方法

・実践してみる

【 createAsyncThunkについて 】

・createAsyncThunkでステート管理

・createAsyncThunkの定義方法

・実践してみる



◆ はじめに

今回の投稿は、Reduxの仕組みをある程度理解していることを前提として内容を記述します。
Reduxをまだわかっていない場合は、以下の投稿を参考にしてください。

◆ Redux Thunkを用いた非同期処理の流れ

スクリーンショット 2023-03-26 19.58.30.png

通常のReduxの動き(黒文字) + Redux Thunkによる追加の動き(赤文字)


① 初期値のステートがUIに表示される。Dispatchを実行するためのイベントをUIで実行する。

② Dispatchを実行する。

③ Middle WareがまずActionを受け取り、非同期処理を行う。

④ データが取得できたら、Middle Wareの中でDispatchをさらに実行し、Reducerを呼び出し、ステートを更新する。

⑤ 更新されたステートが画面に表示される。

◆ Redux Thunkの定義方法

const exampleThunk = (payload) => {
  return (dispatch, getState) => {
    // 副作用処理(非同期処理)
  }
}

【ポイント】

① Redux Thunkでは引数を受け取り、関数を返す。

② 戻り値の関数は、dispatchとgetStateという値を引数にとる。
※getState → 現在のStateの値を取得する関数

③ dispatchは、戻り値の関数内で実行する。

◆実践してみる

スクリーンショット 2023-03-27 0.34.02.png

◆ 模擬サーバー(API)の設定

※自由で可

/**
 * ランダムの秒数後、引数に与えた数値をオブジェクトとして返すAPI
 */
const addAsyncApi = (count = 1) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ data: count }), Math.random() * 1000);
  });
};

export { addAsyncApi };

◆ 更新用関数の定義

import { createSlice } from "@reduxjs/toolkit";
import { addAsyncApi } from "APIを定義したファイルの階層";

const counter = createSlice({
  // Slice名
  name: "counter",
  // 初期値
  initialState: {
    count: 0,
  },
  // reducer詳細
  reducers: {
    add(state, { type, payload }) {
      const newState = { ...state };
      newState.count = state.count + payload;
      return newState;
    },
  },
});
// アクションクリエイターの定義
const { add } = counter.actions;

// redux-thunkの定義
const addThunkFunction = (payload) => {
  // APIがPromise処理なので非同期で実行する = async/await
  return async (dispatch, getState) => {
    // 非同期処理の実行(API呼び出し)
    const response = await addAsyncApi(payload);
    /**
     * dispatchの実行
     * アクションクリエイターの戻り値をもとにDipsatchを実行しているので、ステートが更新される。
     * Redux Thunkを用いた非同期処理の流れ ④の内容
     */ 
    dispatch(add(response.data));
  };
};

export { add, addThunkFunction };
export default counter.reducer;

※非同期処理(async/await)

◆ Storeの定義

import { configureStore } from "@reduxjs/toolkit";
import reducer from "counter.reducerがあるファイルの階層";

export default configureStore({
  reducer: {
    counter: reducer,
  },
});

◆ コンポーネントの定義

・ボタン

import { useDispatch } from "react-redux";
import { addThunkFunction } from "redux thunkを定義したファイルの階層";

const CounterButton = ({ step }) => {
  const dispatch = useDispatch();

  const clickHandler = () => {
    /**
     * dispatchの実行
     * redux thunk(MIddle Ware)をまず実行している。
     * Redux Thunkを用いた非同期処理の流れ ③の内容
     */
    dispatch(addThunkFunction(step));
  };
  return <button onClick={clickHandler}>非同期(+2)</button>;
};

export { CounterButton };

・ステート表示用コンポーネント

import { useSelector } from "react-redux";

const CounterResult = () => {
  const state = useSelector((state) => state.counter.count);
  return <p>{state}</p>;
};

export { CounterResult };

・上記2つをまとめて表示する親コンポーネント

import { CounterResult } from "./CounterResult";
import { CounterButton } from "./CounterButton";

const Counter = () => {
  return (
    <>
      <CounterResult />
      <CounterButton step={2} />
    </>
  );
};

export default Counter;

◆ コンポーネント間の管理ができるよう設定(ラッピング)

import "./App.css";
import { Provider } from "react-redux";
import store from "configureStoreを定義したファイルの階層";
import Counter from "Counterのファイルの階層";

function App() {
  return (
    <div className="App"> 
      { /* 管理対象に含めたいコンポーネントをProviderでラッピング */ }
      <Provider store={store}>
        <Counter />
      </Provider>
    </div>
  );
}

export default App;



◆ createAsyncThunkでステート管理

createAsyncThunkとは、
Reduxで非同期処理を行うもう一つの方法で、アクションクリエイター。
生成するAction typeは、

・pending(保留)
・fullfiled (成功)
・rejected(失敗)

があり、
これらのAction typeに応じて管理しているステートの値を変えたいときに使える。

◆ createAsyncThunkの定義方法

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

const exampleSlice = createSlice({
  // Slice名
  name: example,
  // 初期値
  value: 0,
  // reducer詳細
  reducers: {
    // 更新用メソッド
  },
  // createAsyncThunkで作成されるActionCreatorに対応するReducer
  extraReducers: (builder) => {
    builder
    .addCase(exampleThunk.pending, (state, action) => {/* 実行したい処理 */}
   ).addCase(exampleThunk.fullfiled, (state, action) => {/* 実行したい処理 */}
   ).addCase(exampleThunk.rejected, (state, action) => {/* 実行したい処理 */}
  }
})

const exampleThunk = createAsyncThunk(
  // ※type 一意の文字列であれば良
  'example/asyncFunction',
  // 非同期処理設定
  async (payload) => {
    // 非同期処理
    return // 任意の値
  }
)

※typeは、追加の action type 生成に使われる文字列。
生成されるaction type(pending, fullfiled, rejected)の接頭辞に使われる。

'example/asyncFunction'

pending => 'example/asyncFunction/pending'

fullfiled => 'example/asyncFunction/fullfiled'

rejected => 'example/asyncFunction/rejected'

◆ 実践してみる

スクリーンショット 2023-03-28 23.54.53.png

この回では、先ほどの実装に非同期処理のステータスを表示するものを作成する。

◆ 更新用関数の定義

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { addAsyncApi } from "APIを定義したファイルの階層";

const counter = createSlice({
  // Slice名
  name: "counter",
  // 初期値
  initialState: {
    count: 0,
    status: "",
  },
  // reducer詳細
  reducers: {
    add(state, { type, payload }) {
      const newState = { ...state };
      newState.count = state.count + payload;
      return newState;
    },
  },
  // createAsyncThunkで作成されるActionCreatorに対応するReducer
  extraReducers: (builder) => {
      // 保留時の状態
      builder.addCase(addThunkFunctionWithStatus.pending, (state, action) => {
        // ステータスの更新
        state.status = "Loading...";

      // 成功時の状態
      }).addCase(addThunkFunctionWithStatus.fullfiled, (state, action) => {
        // countの更新
        state.count += action.payload;
        // ステータスの更新
        state.status = "Success";

      // 失敗時の状態
      }).addCase(addThunkFunctionWithStatus.rejected, (state, action) => {
        // ステータスの更新
        state.status = "Error";
    }
  },
});

// アクションクリエイターの定義
const { add } = counter.actions;

// createAsyncThunkの定義
const addThunkFunctionWithStatus = createAsyncThunk(
  // type
  'counter/asyncThunk',
  // 非同期処理設定
  async (payload) => {
    const response = await addAsyncApi(payload);
    /**
     * redux-thunkの時は、redux-thunkはミドルウェアで、
     * ミドルウェア内で再度Dispatchを実行し、
     * そのDispatchの中で定義したアクションクリエイターを実行していたが、
     * 今回はcreateAsyncThunkがアクションクリエイターとなるので、
     * dispatchはせず、returnで値を返すようにする。
     */
    return response.data;
  }
)

export { add, addThunkFunctionWithStatus };
export default counter.reducer;

◆ コンポーネント

・ボタン

import { useDispatch } from "react-redux";
import { addThunkFunctionWithStatus } from "createAsyncThunkを定義したファイルの階層";

const CounterButton = ({ step }) => {
  const dispatch = useDispatch();

  const clickHandler = () => {

    dispatch(addThunkFunctionWithStatus(step));
  };
  return <button onClick={clickHandler}>非同期(+2)</button>;
};

export { CounterButton };

・親コンポーネント

import { CounterResult } from "./CounterResult";
import { CounterButton } from "./CounterButton";
import { useSelector } from "react-redux";

const Counter = () => {
  const status = useSelector(state => state.counter.status);
  return (
    <>
      <CounterResult />
      <CounterButton step={2} />
      <p>{ status }</p>
    </>
  );
};

export default Counter;

◆ 完成

・初期状態
スクリーンショット 2023-03-29 0.32.07.png


・実行中
スクリーンショット 2023-03-29 0.33.43.png


・成功

スクリーンショット 2023-03-29 0.34.22.png

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0