概要
createApi でエンドポイントを定義すると、RTK Query がデータフェッチ・キャッシュ・ローディング状態・自動再フェッチを自動で管理するフックを生成する。createAsyncThunk による手動キャッシュ管理が不要になり、Redux Store 内で TanStack Query 相当の機能を実現できる。
インストール
npm install @reduxjs/toolkit react-redux
実装
API スライスの定義
// store/api/usersApi.ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
type User = { id: number; name: string; email: string };
type CreateUserInput = { name: string; email: string };
export const usersApi = createApi({
reducerPath: "usersApi",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
// キャッシュの無効化タグ
tagTypes: ["User"],
endpoints: (builder) => ({
// GET /api/users → useGetUsersQuery
getUsers: builder.query<User[], void>({
query: () => "/users",
providesTags: ["User"],
}),
// GET /api/users/:id → useGetUserByIdQuery
getUserById: builder.query<User, number>({
query: (id) => `/users/${id}`,
providesTags: (_result, _error, id) => [{ type: "User", id }],
}),
// POST /api/users → useCreateUserMutation
createUser: builder.mutation<User, CreateUserInput>({
query: (body) => ({
url: "/users",
method: "POST",
body,
}),
// 成功時に User タグを持つキャッシュを無効化 → 一覧が自動再フェッチされる
invalidatesTags: ["User"],
}),
// DELETE /api/users/:id → useDeleteUserMutation
deleteUser: builder.mutation<void, number>({
query: (id) => ({
url: `/users/${id}`,
method: "DELETE",
}),
invalidatesTags: ["User"],
}),
}),
});
export const {
useGetUsersQuery,
useGetUserByIdQuery,
useCreateUserMutation,
useDeleteUserMutation,
} = usersApi;
Store への組み込み
// store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { usersApi } from "./api/usersApi";
export const store = configureStore({
reducer: {
[usersApi.reducerPath]: usersApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(usersApi.middleware),
});
export type RootState = ReturnType<typeof store.getState>;
Provider の設定
// app/providers.tsx
"use client";
import { Provider } from "react-redux";
import { store } from "@/store";
export function Providers({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>;
}
Query フック(一覧取得)
// components/UserList.tsx
"use client";
import { useGetUsersQuery, useDeleteUserMutation } from "@/store/api/usersApi";
export function UserList() {
const { data: users, isLoading, isError } = useGetUsersQuery();
const [deleteUser] = useDeleteUserMutation();
if (isLoading) return <p className="text-sm text-gray-500">読み込み中...</p>;
if (isError) return <p className="text-sm text-red-500">エラーが発生しました</p>;
return (
<ul className="space-y-2">
{users?.map((user) => (
<li key={user.id} className="flex items-center justify-between rounded border p-3">
<div>
<p className="font-medium text-gray-800">{user.name}</p>
<p className="text-xs text-gray-500">{user.email}</p>
</div>
<button
onClick={() => deleteUser(user.id)}
className="rounded bg-red-50 px-3 py-1 text-xs text-red-600 hover:bg-red-100"
>
削除
</button>
</li>
))}
</ul>
);
}
Mutation フック(作成)
// components/CreateUserForm.tsx
"use client";
import { useState } from "react";
import { useCreateUserMutation } from "@/store/api/usersApi";
export function CreateUserForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [createUser, { isLoading, isError }] = useCreateUserMutation();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
await createUser({ name, email });
setName("");
setEmail("");
}
return (
<form onSubmit={handleSubmit} className="space-y-3">
<input
type="text"
placeholder="名前"
value={name}
onChange={(e) => setName(e.target.value)}
className="block w-full rounded border px-3 py-2 text-sm"
/>
<input
type="email"
placeholder="メール"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="block w-full rounded border px-3 py-2 text-sm"
/>
<button
type="submit"
disabled={isLoading || !name || !email}
className="rounded bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50"
>
{isLoading ? "追加中..." : "ユーザーを追加"}
</button>
{isError && <p className="text-xs text-red-500">作成に失敗しました</p>}
</form>
);
}
ポイント
createApiのendpointsでエンドポイントを宣言すると、useGetUsersQuery/useCreateUserMutationなどのフックが自動生成される。手動でdispatch(fetchUsers())を書く必要がないprovidesTags/invalidatesTagsでキャッシュの依存関係を宣言すると、Mutation 成功後に関連する Query が自動再フェッチされる(createUser後に一覧が更新される)redux-toolkit-async-thunkとの違い:createAsyncThunkはフェッチ処理と state 更新を手動で書く。RTK Query はエンドポイント定義だけでキャッシュ・ローディング・エラー状態を自動管理するisLoading/isFetching/isError/dataが自動で取れるため、ローディング UI の実装が簡潔になる- TanStack Query との使い分け: Redux を既に使っていてキャッシュも Redux に統一したい場合は RTK Query、Redux を使わない構成では TanStack Query が軽量