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