tanstack-query について
Svelteアプリ開発者向けのTanStack Queryによるデータ取得の指針です。API呼び出しの集約、クエリキーの規約、ミューテーション、WebSocket連動のキャッシュ無効化を扱います。
主な機能
- 01機能ごとのAPI呼び出し集約
- 02クエリキーのファクトリー化
- 03クエリとミューテーションの命名規約
- 04楽観的更新とロールバック
- 05WebSocketメッセージによるキャッシュ無効化
活用シーン
- 01機能ごとのデータリクエスト整理
- 02クエリキーとキャッシュ更新の統一
- 03ロールバック付き楽観的ミューテーションの実装
- 04WebSocketメッセージに応じたクエリ更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ exceptionless/ exceptionless --skill tanstack-queryインストール後、Claude Code で/tanstack-queryを入力して読み込まれたか確認します。
詳細
TanStack Query
ドキュメント: tanstack.com/query。ローカルのパターンだけでは不十分な場合は、公式ドキュメントを参照してください。
@exceptionless/fetchclient と TanStack Query を使用して、機能ごとに api.svelte.ts に API 呼び出しをまとめます。
クエリの基本
// src/lib/features/organizations/api.svelte.ts
import { createQuery, createMutation, useQueryClient } from "@tanstack/svelte-query";
import { type FetchClientResponse, type ProblemDetails, useFetchClient } from "@exceptionless/fetchclient";
import { accessToken } from "$features/auth/index.svelte";
const queryKeys = {
type: ["Organization"] as const,
};
export function getOrganizationsQuery() {
return createQuery<FetchClientResponse<Organization[]>, ProblemDetails>(() => ({
enabled: () => !!accessToken.current,
queryKey: queryKeys.type,
queryFn: async ({ signal }: { signal: AbortSignal }) => {
const client = useFetchClient();
const response = await client.getJSON<Organization[]>("/organizations", { signal });
return response;
},
}));
}
クエリキーの規約
型安全性と一貫性を確保するため、機能ごとに queryKeys ファクトリーを使用します。
export const queryKeys = {
type: ["Webhook"] as const,
id: (id: string | undefined) => [...queryKeys.type, id] as const,
ids: (ids: string[] | undefined) => [...queryKeys.type, ...(ids ?? [])] as const,
project: (id: string | undefined) => [...queryKeys.type, "project", id] as const,
deleteWebhook: (ids: string[] | undefined) => [...queryKeys.ids(ids), "delete"] as const,
postWebhook: () => [...queryKeys.type, "post"] as const,
};
WebSocket による無効化とキャッシュ更新で同じキーを共有できるよう、場当たり的な配列ではなく、その機能の queryKeys ファクトリーを優先して使用してください。
ミューテーション
export function postOrganizationMutation() {
const queryClient = useQueryClient();
return createMutation(() => ({
mutationFn: async (data: CreateOrganizationRequest) => {
const client = useFetchClient();
const response = await client.postJSON<Organization>("/organizations", data);
return response.data!;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.type });
},
}));
}
命名規則
| パターン | 命名 | 例 |
|---|---|---|
| クエリ(GET) | get{Resource}Query | getOrganizationsQuery() |
| 作成(POST) | post{Resource}Mutation | postOrganizationMutation() |
| 更新(PATCH) | patch{Resource}Mutation | patchOrganizationMutation() |
| 削除(DELETE) | delete{Resource}Mutation | deleteOrganizationMutation() |
依存するクエリ
クエリを条件付きで実行するには、enabled を使用します。例:enabled: !!projectId。
楽観的更新
キャッシュされたデータを楽観的に更新するミューテーションでは、onMutate を使用して実行中のクエリをキャンセルし、getQueryData で以前の値のスナップショットを取得してから、setQueryData で楽観的な更新を適用します。onError でスナップショットからロールバックし、onSettled で必ず invalidateQueries を呼び出して最終的に再取得します。
WebSocket による無効化
WebSocket メッセージの受信時にクエリを無効化します。
export async function invalidateWebhookQueries(
queryClient: QueryClient,
message: WebSocketMessageValue<"WebhookChanged">,
) {
const { id, organization_id, project_id } = message;
if (id) await queryClient.invalidateQueries({ queryKey: queryKeys.id(id) });
if (project_id) await queryClient.invalidateQueries({ queryKey: queryKeys.project(project_id) });
if (!id && !organization_id && !project_id)
await queryClient.invalidateQueries({ queryKey: queryKeys.type });
}
アプリのレイアウトから WebSocket メッセージを機能の無効化ヘルパーに接続します。