Redux Toolkit の createAsyncThunk における generics

[Redux Toolkit](https://redux-toolkit.js.org/) を案件で使用する機会があったのでメモ。
TypeScript で createAsyncThunk 宣言時の generics について。

# 前提
middleware はデフォルトの [Redux Thunk](https://github.com/reduxjs/redux-thunk)。

# 結論

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

```ts: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)));
});
```

```ts: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 を返す。

```ts
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);
  }
);
```

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

```ts
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 を使用するには以下のように記述する。

```ts: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 に対して型推論が行われない。
解決するために上述の結論のような書き方をする。