13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Redux Toolkit の createAsyncThunk における generics

Last updated at Posted at 2020-05-18

Redux Toolkit を案件で使用する機会があったのでメモ。
TypeScript で createAsyncThunk 宣言時の generics について。

前提

middleware はデフォルトの Redux Thunk

結論

下記のように記述すると型推論が引数、返り値、thunkApi に対して機能する。

api.ts
import { createAsyncThunk } from '@reduxjs/toolkit'
import { AppDispatch, RootState, userActions } from './store.ts'

interface Response {
  name: string;
  company: string;
}

export const requestApi = createAsyncThunk<
  // 返り値の型
  Response,
  // 引数の型
  { id: number },
  // thunkApi の型
  {
    dispatch: AppDispatch;
    state: RootState;
  }
>("me", async ({ id }, thunkApi) => {
  // store から select する時に型推論が働く
  const { accessToken } = thunkApi.getState().user;

  return fetch(`/users/${id}`, {
    headers: { Authorization: `Bearer: ${accessToken}` },
  })
    .then((res) => res.json())
    .then((json) => json)
    // action を dispatch する時に型推論が働く
    .catch((error) => thunkApi.dispatch(userActions.showError(error)));
});
store.ts
import { configureStore, createSlice } from '@reduxjs/toolkit'

interface UserState {
  accessToken: string;
}

const initialState: UserState = {
  accessToken: "",
};

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    showError: (state, action) => console.error(action.payload),
  },
});

const userReducer = userSlice.reducer;
export const userActions = userSlice.actions;

const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

以下、筋道を説明。

基本的な実装

下記2個のパラメータを引数としてとる。

  • type
  • payload creator

そして thunk action creator なる Promise を返す。

import { createAsyncThunk } from '@reduxjs/toolkit'

export const requestApi = createAsyncThunk(
  "me",
  async (arg: { id: number }) => {
    const id = { arg };
    return fetch(`/users/${id}`)
      .then((res) => res.json())
      .then((json) => json);
  }
);

明示的に引数と返り値の型を宣言する

import { createAsyncThunk } from '@reduxjs/toolkit'

interface Response {
  name: string;
  company: string;
}

export const requestApi = createAsyncThunk<Response, { id: number }>(
  "me",
  async ({ id }) =>
    fetch(`/users/${id}`)
      .then((res) => res.json())
      .then((json) => json)
);

Redux Thunk を middleware として使う

Redux Thunk を使用するには以下のように記述する。

api.ts
import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState, userActions } from './store.ts'

interface Response {
  name: string;
  company: string;
}

export const requestApi = createAsyncThunk<Response, { id: number }>(
  "me",
  async ({ id }, thunkApi) => {
    // getState の型は unknown なので cast が必要
    const accessToken = (thunkApi.getState() as RootState).user.accessToken;
    return fetch(`/users/${id}`, {
      headers: { Authorization: `Bearer: ${accessToken}` },
    })
      .then((res) => res.json())
      .then((json) => json)
      // dispatch は type と payload があれば何でも通ってしまう
      .catch((error) => thunkApi.dispatch(userActions.showError(error)));
  }
);

しかし、Redux の store, dispatch に対して型推論が行われない。
解決するために上述の結論のような書き方をする。

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?