こんにちは。Naotoです。
今回は、Redux Thunkを使ってReduxで非同期処理(その他副作用処理)を扱う方法について説明したいと思います。
※この記事では、TypeScript, Redux Toolkitを使用します。
前提知識
- Reactの基礎知識
- Reduxの基礎知識
学んだきっかけ
個人開発中のタスク管理アプリの実装中、「初期レンダリング時に、APIを叩いてデータベースからタスクやカテゴリなどの情報を取得し、ReduxのStateに反映させる」といったコードを書いていました。
最初に思いついた方法は、トップページのuseEffect内に非同期処理を直接書き、その後、取得した値をReduxのStateにdispatchする方法です。
コードは以下です。
useEffect(() => {
(async () => {
// 未完了タスク取得
const inCompletedTaskItems: TaskItem[] = await taskApi.inCompletedTaskGet(userId);
// 取得した未完了タスクを未完了タスクStateに反映
inCompletedTaskItems.forEach((inCompletedTaskItem) =>
dispatch(inCompletedTaskAdd(inCompletedTaskItem))
);
// 完了タスク取得
const completedTaskItems: TaskItem[] = await taskApi.completedTaskGet(userId);
// 取得した完了タスクを完了タスクStateに反映
completedTaskItems.forEach((completedTaskItem) =>
dispatch(completedTaskAdd(completedTaskItem))
);
// カテゴリ取得
const categories: Category[] = await taskApi.categoryGetAll(userId);
// 取得したカテゴリをカテゴリStateに反映
categories.forEach((category) => dispatch(categoryAdd(category)));
// スケジュール取得
const schedules: Schedule[] = await taskApi.scheduleGetAll(userId);
// 取得したスケジュールをスケジュールStateに反映
schedules.forEach((schedule) => dispatch(scheduleAdd(schedule)));
})();
}, []);
やっていることは単純ですが、ものすごくコードが長くなってしまいます。
そこで、他に何か良い方法が無いか調査したところ、今回紹介する、Redux Thunkに辿り着きました。
Redux Thunkとは?
Redux Thunkとは、タイトルの通り、Reduxで非同期処理を扱えるようにするものです。
通常、Reduxを使ってアプリケーションの状態管理を行う際、Stateを更新するためには、Reducerに更新処理を記述します。しかし、Reducerは純粋関数でないといけないといったルールがあります。
※純粋関数 :以下の性質を持つ関数
- returnが引数のみに依存する。
- その関数外に影響を与えない。(参照・変更しない。)
そのため、「Reducer内でAPIを叩いて、Stateの更新と同時にデータベースを更新する」といったことはできません。
しかしRedux Thunkを使うことで、
非同期処理をはじめとする、DOM操作、コンソールへのログ出力、タイマー処理といった、いわゆる「副作用」をReduxで扱えるようになります。
(Redux Thunkを用いて記述した副作用は、ReducerではなくMiddlewareという部分で扱われます。)
Redux Thunkを使用したコード
Redux Thunk関数の定義
基本形は以下になります。
以下、reduxThunkFnが実行されると、payloadとして引数を受け取り、同時にdispatch関数、getState関数を引数とする関数が生成されreturnされます。
そして、returnされる関数内で、副作用の処理を行うことができます。
const reduxThunkFn = (payload) => {
return (dispatch, getState) => {
副作用処理
}
}
では、今回は、未完了タスクをAPIから取得し、ReduxのStateに反映する場合について説明します。
以下は、未完了タスクのSlicerにRedux Thunk関数を定義した例です。
import { TaskItem } from "../@types";
import { Dispatch, createSlice } from "@reduxjs/toolkit";
import taskApi from "../api/task";
// 未完了タスクState////////////////////////////////////////////////////////////////////
// 初期値
const initialState: TaskItem[] = [];
// Slice作成
export const inCompletedTaskItemsSlice = createSlice({
name: "inCompletedTaskItems",
initialState,
reducers: {
// タスク追加
inCompletedTaskAdd: (state, action) => {
state.push(action.payload);
},
},
});
// ここでRedux Thunk関数を定義(TypeScriptを使用しているため、型情報を追記しています。)
const getAllInCompletedTaskItems = (payload: string) => {
return async (dispatch: Dispatch, getState: () => TaskItem) => {
// ここで、APIを叩きデータベースから未完了タスクを取得しています。(副作用処理)
const inCompletedTaskItems: TaskItem[] = await taskApi.inCompletedTaskGet(payload);
// 引数のdispatch関数を使用し、取得した未完了タスクをStateに反映
inCompletedTaskItems.forEach((inCompletedTaskItem) =>
dispatch(inCompletedTaskAdd(inCompletedTaskItem))
);
};
};
// Redux Thunk関数をexport
export { getAllInCompletedTaskItems };
export default inCompletedTaskItemsSlice.reducer;
上記により、データベースからのデータ取得とReduxのState更新を同時に行える、getAllInCompletedTaskItems関数が完成し、他コンポーネントから使用可能となりました。
同じように、完了タスク、カテゴリー、スケジュールのSliceにもRedux Thunk関数を定義します。(コードは割愛)
Redux Thunk関数の使用
上記でexportしたそれぞれのRedux Thunk関数をトップページのuseEffect内で呼び出します。
useEffect(() => {
(async () => {
// APIから未完了タスクを取得&Stateに反映
dispatch(getAllInCompletedTaskItems(userId));
// APIから完了タスクを取得&Stateに反映
dispatch(getAllCompletedTaskItems(userId));
// APIからカテゴリを取得&Stateに反映
dispatch(getAllCategories(userId));
// APIからスケジュールを取得&Stateに反映
dispatch(getAllSchedules(userId));
})();
}, []);
コードが冒頭のコードと比べてとても簡潔になりました。
まとめ
このように、Redux Thunkを使用することで、非同期処理をReduxのアクションとして管理し、コードを整理することができます。
ぜひ、皆さんもRedux Thunkを活用して、Reduxでの非同期処理を効率化してみてください!!!
最後までお読みいただきありがとうございました!
ps. 週2投稿を目指していますが、最近週1が限界です。。。頑張ります。。。。