【React】 Reduxで非同期に値を更新
Reduxの特徴の一つは、素のReactでは困難な非同期処理でのステート更新が可能なことです。
特に、@reduxjs/toolkit
を用いると楽に実装することができます。
まだReduxの環境や基礎が整っていない場合は、先にそれを確認することをおすすめします
非同期な更新
とりあえず事前準備として、普通の更新を用意しておきます。
import { createSlice } from '@reduxjs/toolkit'; const initialState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export const selectCount = (state) => state.counter.value; export default counterSlice.reducer;
下で作成したものと同じです。
createAsyncThunkで作成
非同期に値を更新するためには、まずはcreateAsyncThunk
を用いて、更新用の処理を作成します。
export const fetchCount = (amount) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ data: amount }); }, 1000); }); }
export const incrementAsync = createAsyncThunk( // slice名/適当な名前 でok 'counter/fetchCount', // 処理の本体 async (amount) => { const response = await fetchCount(amount); return response.data; } );
Promiseやasync/awaitについては下の記事にあります
作成と同時にexport
して、dispatch
で呼び出す準備をしておきましょう。
このときにreturn
で返した値が、action
の値として入ってきます。
reducerに登録
createAsyncThunk
で作成したものは、createSlice
内で、extraReducers
で処理を登録します。
const initialState = { value: 0, status: "idle" }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { // ... }, extraReducers: (builder) => { builder .addCase(incrementAsync.pending, (state) => { state.status = "loading"; }) .addCase(incrementAsync.fulfilled, (state, action) => { state.status = "idle"; state.value += action.payload; }); }, }); // ついでに追加したstateをexport export const selectUpdateStatus = (state) => state.counter.status;
builder.addCase
が登場しました。これは、指定したAsyncThunk
の状態が変化した際に、どのような処理を行うかを登録するためのものです。感覚はaddEventListener
と似ています。
状態には3つあります。
値 | 状態 |
---|---|
.pending | 実行は開始したが、まだ非同期処理が終わっていない状態 |
.fulfilled | 実行が正常に完了した (resolveされた) |
.rejected | 実行が失敗した (rejectされた) |
もちろん、addCase
を増やすだけで、別のAsyncThunk
を追加できます。
dispatchする
処理ができたら、dispatch
を使用して非同期処理を開始します。
import { useSelector, useDispatch } from 'react-redux'; import { increment, incrementAsync, selectCount } from './features/counter/counterSlice'; export default function App() { const count = useSelector(selectCount); const status = useSelector(selectUpdateStatus); const dispatch = useDispatch(); const handleIncrementByAmountAsync = () => { // 他と同じように呼ぶだけ dispatch(incrementAsync(5)); } return ( <> <p>{ count }</p> <p>{ status }</p> <button onClick={handleIncrementByAmountAsync}>+5 (async)</button> </> ); }
そうすると、一定時間後に数値が加算されることがわかります。また、非同期処理中に値が更新されても問題なく値が変化することがわかります。
まとめ
@reduxjs/toolkit
を用いると、簡単にReduxで値の非同期な更新が可能になります。また、同期的な更新と混ぜても問題ありません。
非同期更新は非常に頻繁に行うので、書き方をしっかり把握しておきましょう。
