createAsyncThunkはRedux Toolkit 1.3.0から利用できる非同期処理用の機能です。
前回、Todoアプリで理解するRedux Thunkによる非同期処理の実装方法でRedux Thunk(ThunkAction)を利用したReduxの非同期処理の実装方法について紹介しました。
今回はcreateAsyncThunkを利用した非同期処理の実装方法について紹介をします。
なお、createAsyncThunkのライフサイクルを理解するにはPromiseの理解が必須です。
Promiseの概要についてはJavaScriptの非同期処理で理解必須!Promiseの概要・挙動まとめで紹介をしているので、そちらもあわせてご覧になってください。
reduxjs/toolkit
は1.5.0を利用しています。
また、今回紹介するサンプルコードはnishina555/reduxtoolkit-thunk-todoappに公開しております。
目次
createAsyncThunkについて
createAsyncThunkとは非同期処理の実行状況に応じたActionCreatorを生成する関数です。
createAsyncThunkは2つのパラメータを持ちます。
第1引数はtypePrefixと呼ばれる文字列です。typePrefixはActionTypeの接頭辞として利用されます。
第2引数はpayloadCreatorと呼ばれるasync関数(Promiseを返す非同期処理の関数)です。
createAsyncThunkはasync関数で処理されるPromiseの状態に応じたActionCreatorを返します。
createAsyncThunkに関する戻り値
createAsyncThunkによって作成されるActionCreator、およびActionCreatorがdispatchするActionオブジェクトの値について紹介します。
戻り値の詳細はRedux Toolkit『createAsyncThunk#Return Value』をご覧になってください。
createAsyncThunkが返すActionCreatorについて
Promiseオブジェクトは以下の3種類の状態を持ちます。
- pending(保留)
- fulfilled(成功)
- rejected(失敗)
createAsyncThunkのメソッド名がfetchAllTodos
だった場合、Promiseオブジェクトの状態に応じて以下のActionCreatorが返されます。
- pendingの時: ‘fetchAllTodos.pending’
- fulfilledの時: ‘fetchAllTodos.fulfilled’
- rejectedの時: ‘fetchAllTodos.rejected’
createAsyncThunkによって作成されたActionCreatorがdispatchするActionについて
たとえばtodos/fetchAllTodos
というtypePrefixの場合、ActionCreatorによって作成されるActionTypeは以下の通りです。
- pendingの時: ‘todos/fetchAllTodos/pending’
- fulfilledの時: ‘todos/fetchAllTodos/fulfilled’
- rejectedの時: ‘todos/fetchAllTodos/rejected’
ActionのpayloadはpayloadCreatorの結果にもとづき決定されます。
async関数でPromiseオブジェクトがresoloveした場合(任意の値がreturnされた場合)、fulfilledなActionTypeが作成されます。async関数の戻り値はaction.payload
に保存されます。
ascyn関数でrejectWithValue(value)
がreturnされた場合、rejectedなActionTypeが作成さます。rejectWithValue
はaction.payload
に保存されます。
async関数でPromiseオブジェクトがrejectされた場合、rejectedなActionTypeが作成されます。エラー内容はaction.error
に保存されます。
createAsyncThunkの利用方法について
createAsyncThunkはあくまでActionCreatorを返すだけですので、ActionCreatorに対応したReducerを別途実装する必要があります。
createAsyncThunkで作成されるActionCreatorに対応するReducerはcreateSliceのextraReducerで定義します。
たとえば、非同期処理が正常に終了した場合(Promiseオブジェクトがresolveされた場合)のReducerは以下のように実装します。
src/redux/todoSlice.ts
// API経由でTodoリストを取得するメソッド
// 非同期処理の状態に応じて、fetchAllTodos.pending のようなActionCreatorが作成される
export const fetchAllTodos = createAsyncThunk(
"todos/fetchAllTodos", // typePrefix: 非同期処理の状態に応じて、todos/fetchAllTodos/pending のようなActionTypeが作成される
async () => { // payloadCreator
const response = await axios.get(`http://xxxxxx/todos`) // API経由でTodoリスト取得する
return { todos: response.data }; // action.payloadとしてReducerで読み取れる
}
);
const initialState: TodoState = {
todoItems: [],
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
(略)
},
extraReducers: (builder) => {
// fetchAllTodosというcreateAsyncThunkが正常終了した場合のReducer
builder.addCase(fetchAllTodos.fulfilled, (state, action) => {
state.todoItems = action.payload.todos; // payloadCreatorでreturnされた値
});
},
});
export default todosSlice.reducer;
createAsyncThunkでは非同期処理のステータスに応じたAdctionCreatorが生成されます。
そのため、createAsyncThunkを利用することで非同期処理のステータスに応じたActionのdispatchが簡単に実装できます。
createAsyncThunkの型定義について
createAsyncThunkの型定義はcreateAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>
です。
各パラメータの意味は以下の通りです。
- Returnd: payloadCreator(createAsyncThunkの第二引数の関数)の戻り値
- ThunkArg: payloadCreator(createAsyncThunkの第二引数の関数)の第一引数の型
- ThunkApiConfig: thunkAPIの型
thunkAPIとはThunk関数でReduxの情報を利用できるようにするオブジェクトです。1
Thunk関数でReduxのStateやDispatchを利用する場合にthunkAPIを利用します。ThunkApiConfigでは{dispatch?, state?, extra?, rejectValue?}
の指定が可能です。2
型定義をしたcreateAsyncThunkの具体例は以下の通りです。
src/redux/todoSlice.ts
// API経由でTodoリストを取得するメソッド
export const fetchAllTodos = createAsyncThunk<
{ todos: TodoItem[] } // payloadCreatorの戻り値の型(ここでいうdataはTodoItem[]になる)
>("todos/fetchAllTodos",
async () => {
const response = await axios.get(`http://xxxxxx/todos`);
return { todos: response.data };
}
);
// API経由でTodoを保存するメソッド
export const postTodo = createAsyncThunk<
{ id: number; content: string }, // payloadCreatorの戻り値の型
string, // payloadCreatorの引数(ここでいうcontent)の型
{ state: RootState } // thunkApiの型
>("todos/postTodo", async (content, thunkAPI) => {
const { todos } = thunkAPI.getState(); // ReduxのStateはthunkAPI.getStateで取得する
const todo: PostTodoItem = {
content: content,
completed: false,
};
await TodosApiService.post(todo);
return {
id: todos.todoItems.length + 1,
content: todo.content,
};
});
// API経由でTodoを更新するメソッド
export const patchTodo = createAsyncThunk<
{ id: number }, // payloadCreatorの戻り値
TodoItem // payloadCreatorの引数(ここでいうtodo)の型
>("todos/patchTodo",
async (todo) => {
await TodosApiService.toggle(todo);
return {
id: todo.id,
};
}
);
const initialState: TodoState = {
todoItems: [],
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
(略)
},
extraReducers: (builder) => {
builder.addCase(fetchAllTodos.fulfilled, (state, action) => {
state.todoItems = action.payload.todos;
});
builder.addCase(postTodo.fulfilled, (state, action) => {
state.todoItems = [
...state.todoItems,
{
id: action.payload.id,
content: action.payload.content,
completed: false,
},
];
});
builder.addCase(patchTodo.fulfilled, (state, action) => {
const id = action.payload.id;
const todoItems = state.todoItems.map((todo, index) => {
if (index === id - 1) {
return Object.assign({}, todo, { completed: !todo.completed });
} else {
return todo;
}
});
state.todoItems = todoItems;
});
},
});
export default todosSlice.reducer;
さいごに
- createAsyncThunkはRedux Toolkitで提供されている非同期処理のためのAPI
- createAsyncThunkは非同期処理の状態に応じたActionCreatorを返す
- createAsyncThunkのReducerはcreateSliceのextraReducersで実装する
createAsynThunkを利用したTodoアプリのサンプルコードはnishina555/reduxtoolkit-thunk-todoappに公開しました。以下のような画面のTodoアプリとなっています。
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!