◆目次
【 Redux-Thunkについて 】
・はじめに
・Redux Thunkを用いた非同期処理の流れ
・Redux Thunkの定義方法
・実践してみる
【 createAsyncThunkについて 】
・createAsyncThunkでステート管理
・createAsyncThunkの定義方法
・実践してみる
◆ はじめに
今回の投稿は、Reduxの仕組みをある程度理解していることを前提として内容を記述します。
Reduxをまだわかっていない場合は、以下の投稿を参考にしてください。
◆ Redux Thunkを用いた非同期処理の流れ
通常の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は、戻り値の関数内で実行する。
◆実践してみる
◆ 模擬サーバー(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'
◆ 実践してみる
この回では、先ほどの実装に非同期処理のステータスを表示するものを作成する。
◆ 更新用関数の定義
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;
◆ 完成
・成功