Redux ToolkitのcreateAsyncThunkで非同期処理を実装する方法

JavaScript

createAsyncThunkはRedux Toolkit 1.3.0から利用できる非同期処理用の機能です。

前回、Todoアプリで理解するRedux Thunkによる非同期処理の実装方法でRedux Thunk(ThunkAction)を利用したReduxの非同期処理の実装方法について紹介しました。

Todoアプリで理解するRedux Thunkによる非同期処理の実装方法

2021年3月4日

今回はcreateAsyncThunkを利用した非同期処理の実装方法について紹介をします。

なお、createAsyncThunkのライフサイクルを理解するにはPromiseの理解が必須です。
Promiseの概要についてはJavaScriptの非同期処理で理解必須!Promiseの概要・挙動まとめで紹介をしているので、そちらもあわせてご覧になってください。

JavaScriptの非同期処理で理解必須!Promiseの概要・挙動まとめ

2021年2月28日

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種類の状態を持ちます。

Promiseオブジェクトが持つ状態
  • pending(保留)
  • fulfilled(成功)
  • rejected(失敗)

createAsyncThunkのメソッド名がfetchAllTodosだった場合、Promiseオブジェクトの状態に応じて以下のActionCreatorが返されます。

createAsyncThunkが返すActionCreator
  • pendingの時: ‘fetchAllTodos.pending’
  • fulfilledの時: ‘fetchAllTodos.fulfilled’
  • rejectedの時: ‘fetchAllTodos.rejected’

createAsyncThunkによって作成されたActionCreatorがdispatchするActionについて

たとえばtodos/fetchAllTodosというtypePrefixの場合、ActionCreatorによって作成されるActionTypeは以下の通りです。

ActionCreatorがdispatchする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が作成さます。rejectWithValueaction.payloadに保存されます。

async関数でPromiseオブジェクトがrejectされた場合、rejectedなActionTypeが作成されます。エラー内容はaction.errorに保存されます。

createAsyncThunkの利用方法について

createAsyncThunkはあくまでActionCreatorを返すだけですので、ActionCreatorに対応したReducerを別途実装する必要があります。

createAsyncThunkで作成されるActionCreatorに対応するReducerはcreateSliceのextraReducerで定義します。
たとえば、非同期処理が正常に終了した場合(Promiseオブジェクトがresolveされた場合)のReducerは以下のように実装します。

src/reduc/todoSlice.ts

// API経由でTodoリストを取得するメソッド
// 非同期処理の状態に応じて、fetchAllTodos.pending のようなActionCreatorが作成される
export const fetchAllTodos = createAsyncThunk(
  "todos/fetchAllTodos", // typePrefix: 非同期処理の状態に応じて、todos/fetchAllTodos/pending のようなActionTypeが作成される
  async () => { // payloadCreator
    const data = await axios.get(`http://xxxxxx/todos`) // API経由でTodoリスト取得する
    return { todos: 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 = {}>です。
各パラメータの意味は以下の通りです。

createAsyncThunkの型定義
  • 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/reduc/todoSlice.ts

// API経由でTodoリストを取得するメソッド
export const fetchAllTodos = createAsyncThunk<
  { todos: TodoItem[] } // payloadCreatorの返却値の型(ここでいうdataはTodoItem[]になる)
>("todos/fetchAllTodos",
  async () => {
    const data = await TodosApiService.getAll();
    return { todos: 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)やってます。フォローしてもらえるとうれしいです!

参考記事