跳到主内容
智客 ZICQ

技能库 智客分类:Agent 工作流 tanstack-query

Tanstack Query

强大的同步状态管理,服务器状态公用,以及用于TS/JS,React,Vue,Solid,Svelte & Angular的数据获取.

10435 安装量

官方网址:skills.sh

技能介绍

先看中文介绍;官方 description 原文单独保留,不改写 SKILL.md。

做什么

强大的同步状态管理,服务器状态公用,以及用于TS/JS,React,Vue,Solid,Svelte & Angular的数据获取.

何时用

官方 description 未单独写出 Use when。按规范,代理会在用户任务与这段 description 的关键词匹配时激活本技能。

代理如何加载

按 Agent Skills 渐进披露:启动时只加载 name 与 description(约 100 token);任务匹配后才读入整份 SKILL.md 正文;scripts/、references/、assets/ 仅在需要时再读。 本文件正文结构:Overview、Installation、Setup、Core Concepts、Query Keys、Query Functions。

文件分析

文件分析:这是一份仅含 SKILL.md 的指令型技能,代理激活后整份正文进入上下文。 正文约 843 行,超过规范建议的 500 行,激活时仍会整份载入。

官方 description(原文)

Powerful asynchronous state management, server-state utilities, and data fetching for TS/JS, React, Vue, Solid, Svelte & Angular.

OverviewInstallationSetupCore ConceptsQuery KeysQuery FunctionsqueryOptions HelperQueries (useQuery)Basic UsageQuery OptionsMutations (useMutation)Basic Usage

来源分类:skills.sh agent-skill

SKILL.md 与 Agent 调用

官方规范 ↗
name
tanstack-query
description
Powerful asynchronous state management, server-state utilities, and data fetching for TS/JS, React, Vue, Solid, Svelte & Angular.
  1. 发现技能客户端向 Agent 提供名称与描述目录。
  2. 匹配与调用用户指定或任务匹配后,载入 SKILL.md 指令。
  3. 按需加载按步骤读取参考文档、使用脚本与素材。

具体调用语法与可用工具以目标 Agent 客户端为准。 查看调用机制说明 ↗

安装这个技能

Skills CLI ↗

先选择目标 Agent 和安装范围,保留技能包的附属文件,安装后检查客户端能否发现该技能。

交给 Agent 安装

复制安装指令给支持 Agent Skills 的代理,确认其中的目标目录与客户端匹配。

把 Agent Skill「tanstack-query」安装到我的项目:SKILL.md 原文与官方 description 见 https://zicq.com/zh/skills/skl-d760f7c3a8f04e46-Tanstack-Query.html
请存为 .cursor/skills/tanstack-query/SKILL.md 或 .claude/skills/tanstack-query/SKILL.md,frontmatter 的 name 与 description 保持原样,不要改写。

GitHub 完整包 ↗

终端安装 · Skills CLI

需要 Node.js 与 npx。先查看仓库技能列表,确认实际名称。

npx skills add 'https://github.com/tanstack-skills/tanstack-skills' --list

npx skills add 'https://github.com/tanstack-skills/tanstack-skills' --skill 'tanstack-query'

CLI 会交互选择目标 Agent,默认安装到项目;用户级安装使用 -g。先通过查看命令核对仓库内容,再用 npx skills list 检查已安装技能。

阅读排版
--- name: tanstack-query description: Powerful asynchronous state management, server-state utilities, and data fetching for TS/JS, React, Vue, Solid, Svelte & Angular. --- ## Overview TanStack Query (formerly React Query) manages server state - data that lives on the server and needs to be fetched, cached, synchronized, and updated. It provides automatic caching, background refetching, stale-while-revalidate patterns, pagination, infinite scrolling, and optimistic updates out of the box. **Package:** `@tanstack/react-query` **Devtools:** `@tanstack/react-query-devtools` **Current Version:** v5 ## Installation ```bash npm install @tanstack/react-query npm install -D @tanstack/react-query-devtools # Optional ``` ## Setup ```tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60, // 1 minute gcTime: 1000 * 60 * 5, // 5 minutes (garbage collection) retry: 3, refetchOnWindowFocus: true, refetchOnReconnect: true, }, }, }) function App() { return ( ) } ``` ## Core Concepts ### Query Keys Query keys uniquely identify cached data. They must be serializable arrays: ```tsx // Simple key useQuery({ queryKey: ['todos'], queryFn: fetchTodos }) // With variables (dependency array pattern) useQuery({ queryKey: ['todos', { status, page }], queryFn: fetchTodos }) // Hierarchical keys for invalidation useQuery({ queryKey: ['todos', todoId], queryFn: () => fetchTodo(todoId) }) useQuery({ queryKey: ['todos', todoId, 'comments'], queryFn: () => fetchComments(todoId) }) // Invalidation matches prefixes: // queryClient.invalidateQueries({ queryKey: ['todos'] }) // ^ Invalidates ALL queries starting with 'todos' ``` ### Query Functions ```tsx // Query function receives a QueryFunctionContext useQuery({ queryKey: ['todos', todoId], queryFn: async ({ queryKey, signal, meta }) => { const [_key, id] = queryKey const response = await fetch(`/api/todos/${id}`, { signal }) if (!response.ok) throw new Error('Failed to fetch') return response.json() }, }) // Using the signal for automatic cancellation useQuery({ queryKey: ['todos'], queryFn: async ({ signal }) => { const response = await fetch('/api/todos', { signal }) return response.json() }, }) ``` ### queryOptions Helper Create reusable, type-safe query configurations: ```tsx import { queryOptions } from '@tanstack/react-query' export const todosQueryOptions = queryOptions({ queryKey: ['todos'], queryFn: fetchTodos, staleTime: 5000, }) export const todoQueryOptions = (todoId: string) => queryOptions({ queryKey: ['todos', todoId], queryFn: () => fetchTodo(todoId), enabled: !!todoId, }) // Usage const { data } = useQuery(todosQueryOptions) const { data } = useSuspenseQuery(todoQueryOptions(id)) await queryClient.prefetchQuery(todosQueryOptions) ``` ## Queries (useQuery) ### Basic Usage ```tsx import { useQuery } from '@tanstack/react-query' function Todos() { const { data, error, isLoading, // First load, no data yet isFetching, // Any fetch in progress (including background) isError, isSuccess, isPending, // No data yet (same as isLoading in most cases) status, // 'pending' | 'error' | 'success' fetchStatus, // 'fetching' | 'paused' | 'idle' refetch, isStale, isPlaceholderData, dataUpdatedAt, errorUpdatedAt, } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos, }) if (isLoading) return if (isError) return return } ``` ### Query Options ```tsx useQuery({ queryKey: ['todos'], queryFn: fetchTodos, // Freshness staleTime: 5000, // ms data stays fresh (default: 0) gcTime: 300000, // ms unused data stays in cache (default: 5 min) // Refetching refetchInterval: 10000, // Poll every 10s refetchIntervalInBackground: false, // Don't poll when tab hidden refetchOnMount: true, // Refetch on component mount if stale refetchOnWindowFocus: true, // Refetch on window focus if stale refetchOnReconnect: true, // Refetch on network reconnect // Retry retry: 3, // Number of retries (or function) retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Conditional enabled: !!userId, // Only run when truthy // Initial/placeholder data initialData: () => cachedData, initialDataUpdatedAt: Date.now() - 10000, placeholderData: (previousData) => previousData, // keepPreviousData pattern placeholderData: initialTodos, // Transform select: (data) => data.filter(todo => !todo.done), // Structural sharing (default: true) structuralSharing: true, // Network mode networkMode: 'online', // 'online' | 'always' | 'offlineFirst' // Meta (accessible in query function context) meta: { purpose: 'user-facing' }, }) ``` ## Mutations (useMutation) ### Basic Usage ```tsx import { useMutation, useQueryClient } from '@tanstack/react-query' function AddTodo() { const queryClient = useQueryClient() const mutation = useMutation({ mutationFn: (newTodo: { title: string }) => { return fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo), }).then(res => res.json()) }, // Lifecycle callbacks onMutate: async (variables) => { // Called before mutationFn // Good for optimistic updates return { previousTodos } // context for onError }, onSuccess: (data, variables, context) => { // Invalidate related queries queryClient.invalidateQueries({ queryKey: ['todos'] }) }, onError: (error, variables, context) => { // Rollback optimistic updates queryClient.setQueryData(['todos'], context.previousTodos) }, onSettled: (data, error, variables, context) => { // Always runs (success or error) queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) return ( mutation.mutate({ title: 'New Todo' })} disabled={mutation.isPending} > {mutation.isPending ? 'Adding...' : 'Add Todo'} ) } ``` ### Mutation State ```tsx const { mutate, // Fire-and-forget mutateAsync, // Returns promise isPending, // Mutation in progress isError, isSuccess, isIdle, // Not yet fired data, // Success response error, // Error object reset, // Reset state to idle variables, // Variables passed to mutate status, // 'idle' | 'pending' | 'error' | 'success' } = useMutation({ ... }) ``` ## Optimistic Updates ```tsx const mutation = useMutation({ mutationFn: updateTodo, onMutate: async (newTodo) => { // 1. Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] }) // 2. Snapshot previous value const previousTodo = queryClient.getQueryData(['todos', newTodo.id]) // 3. Optimistically update queryClient.setQueryData(['todos', newTodo.id], newTodo) // 4. Return context for rollback return { previousTodo } }, onError: (err, newTodo, context) => { // Rollback on error queryClient.setQueryData(['todos', newTodo.id], context.previousTodo) }, onSettled: () => { // Always refetch to sync with server queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) ``` ### Optimistic Updates on Lists ```tsx onMutate: async (newTodo) => { await queryClient.cancelQueries({ queryKey: ['todos'] }) const previousTodos = queryClient.getQueryData(['todos']) queryClient.setQueryData(['todos'], (old) => [...old, newTodo]) return { previousTodos } }, onError: (err, newTodo, context) => { queryClient.setQueryData(['todos'], context.previousTodos) }, ``` ## Query Invalidation ```tsx const queryClient = useQueryClient() // Invalidate all queries queryClient.invalidateQueries() // Invalidate by prefix queryClient.invalidateQueries({ queryKey: ['todos'] }) // Invalidate exact match queryClient.invalidateQueries({ queryKey: ['todos', 1], exact: true }) // Invalidate with predicate queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === 'todos' && query.queryKey[1]?.status === 'done', }) // Invalidate and refetch immediately queryClient.refetchQueries({ queryKey: ['todos'] }) // Remove from cache entirely queryClient.removeQueries({ queryKey: ['todos', 1] }) // Reset to initial state queryClient.resetQueries({ queryKey: ['todos'] }) ``` ## Infinite Queries ```tsx import { useInfiniteQuery } from '@tanstack/react-query' function InfiniteList() { const { data, fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage, isFetchingNextPage, isFetchingPreviousPage, } = useInfiniteQuery({ queryKey: ['projects'], queryFn: async ({ pageParam }) => { const res = await fetch(`/api/projects?cursor=${pageParam}`) return res.json() }, initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) => { return lastPage.nextCursor ?? undefined // undefined = no more pages }, getPreviousPageParam: (firstPage, allPages, firstPageParam) => { return firstPage.prevCursor ?? undefined }, maxPages: 3, // Keep max 3 pages in cache (for performance) }) return (
{data.pages.map((page) => page.items.map((item) => ) )} fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} > {isFetchingNextPage ? 'Loading...' : hasNextPage ? 'Load More' : 'No more'}
) } ``` ## Parallel Queries ```tsx // Multiple independent queries run in parallel automatically function Dashboard() { const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers }) const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: fetchProjects }) // Both fetch simultaneously } // Dynamic parallel queries with useQueries function UserProjects({ userIds }) { const queries = useQueries({ queries: userIds.map((id) => ({ queryKey: ['user', id], queryFn: () => fetchUser(id), })), combine: (results) => ({ data: results.map(r => r.data), pending: results.some(r => r.isPending), }), }) } ``` ## Dependent Queries ```tsx // Sequential queries using enabled function UserPosts({ userId }) { const userQuery = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), }) const postsQuery = useQuery({ queryKey: ['posts', userId], queryFn: () => fetchPostsByUser(userId), enabled: !!userQuery.data, // Only run when user is loaded }) } ``` ## Paginated Queries ```tsx function PaginatedList() { const [page, setPage] = useState(1) const { data, isPlaceholderData } = useQuery({ queryKey: ['todos', page], queryFn: () => fetchTodos(page), placeholderData: (previousData) => previousData, // Keep showing old data }) return (
{data.items.map(item => )} setPage(p => p + 1)} disabled={isPlaceholderData || !data.hasMore} > Next
) } ``` ## Suspense Integration ```tsx import { useSuspenseQuery, useSuspenseInfiniteQuery } from '@tanstack/react-query' // Component will suspend until data is loaded function TodoList() { const { data } = useSuspenseQuery({ queryKey: ['todos'], queryFn: fetchTodos, }) // data is guaranteed to be defined here return
    {data.map(todo =>
  • {todo.title}
  • )}
} // Wrap with Suspense boundary function App() { return ( }> }> ) } // Multiple suspense queries (fetch in parallel) function Dashboard() { const [{ data: users }, { data: projects }] = useSuspenseQueries({ queries: [ { queryKey: ['users'], queryFn: fetchUsers }, { queryKey: ['projects'], queryFn: fetchProjects }, ], }) } ``` ## Prefetching ```tsx const queryClient = useQueryClient() // Prefetch on hover function TodoLink({ todoId }) { const prefetch = () => { queryClient.prefetchQuery({ queryKey: ['todo', todoId], queryFn: () => fetchTodo(todoId), staleTime: 5000, // Only prefetch if data older than 5s }) } return ( Todo {todoId} ) } // Prefetch in route loader (TanStack Router integration) export const Route = createFileRoute('/todos/$todoId')({ loader: ({ context: { queryClient }, params: { todoId } }) => queryClient.ensureQueryData(todoQueryOptions(todoId)), }) // Prefetch infinite queries queryClient.prefetchInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, pages: 3, // Prefetch first 3 pages }) ``` ## SSR & Hydration ### Server-Side Prefetching ```tsx // Server component or loader import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query' async function getServerSideProps() { const queryClient = new QueryClient() await queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: fetchTodos, }) return { props: { dehydratedState: dehydrate(queryClient), }, } } function Page({ dehydratedState }) { return ( ) } ``` ### Streaming SSR (React Server Components) ```tsx import { dehydrate, HydrationBoundary } from '@tanstack/react-query' import { makeQueryClient } from './query-client' export default async function Page() { const queryClient = makeQueryClient() // Prefetch on server await queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: fetchTodos, }) return ( ) } ``` ## QueryClient API ```tsx const queryClient = useQueryClient() // Get cached data queryClient.getQueryData(['todos']) // Set cached data queryClient.setQueryData(['todos'], updatedTodos) queryClient.setQueryData(['todos'], (old) => [...old, newTodo]) // Get query state queryClient.getQueryState(['todos']) // Check if fetching queryClient.isFetching({ queryKey: ['todos'] }) queryClient.isMutating() // Cancel queries queryClient.cancelQueries({ queryKey: ['todos'] }) // Invalidate (marks stale, refetches active) queryClient.invalidateQueries({ queryKey: ['todos'] }) // Refetch (force refetch even if fresh) queryClient.refetchQueries({ queryKey: ['todos'] }) // Remove from cache queryClient.removeQueries({ queryKey: ['todos'] }) // Reset to initial state queryClient.resetQueries({ queryKey: ['todos'] }) // Clear entire cache queryClient.clear() // Prefetch queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: fetchTodos }) queryClient.ensureQueryData({ queryKey: ['todos'], queryFn: fetchTodos }) // Get/set defaults queryClient.setQueryDefaults(['todos'], { staleTime: 10000 }) queryClient.getQueryDefaults(['todos']) queryClient.setMutationDefaults(['addTodo'], { mutationFn: addTodo }) ``` ## Testing ```tsx import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' function createWrapper() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, // Don't retry in tests gcTime: Infinity, // Prevent garbage collection during tests }, }, }) return ({ children }) => ( {children} ) } test('fetches todos', async () => { const { result } = renderHook(() => useQuery({ queryKey: ['todos'], queryFn: fetchTodos, }), { wrapper: createWrapper() }) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toEqual(expectedTodos) }) // Mock with setQueryData for component tests test('renders todos', () => { const queryClient = new QueryClient() queryClient.setQueryData(['todos'], mockTodos) render( ) expect(screen.getByText('Todo 1')).toBeInTheDocument() }) ``` ## TypeScript Patterns ### Typing Query Functions ```tsx interface Todo { id: number title: string completed: boolean } // Type is inferred from queryFn return type const { data } = useQuery({ queryKey: ['todos'], queryFn: async (): Promise => { const res = await fetch('/api/todos') return res.json() }, }) // data: Todo[] | undefined // With select const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos, select: (data): string[] => data.map(t => t.title), }) // data: string[] | undefined ``` ### Typing Errors ```tsx // Default error type is Error const { error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos, }) // Or register globally declare module '@tanstack/react-query' { interface Register { defaultError: AxiosError } } ``` ### Query Options Pattern (Recommended) ```tsx import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query' export const todosOptions = queryOptions({ queryKey: ['todos'] as const, queryFn: fetchTodos, staleTime: 5000, }) export const todoOptions = (id: string) => queryOptions({ queryKey: ['todos', id] as const, queryFn: () => fetchTodo(id), enabled: !!id, }) // Full type inference everywhere const { data } = useQuery(todosOptions) const { data } = useSuspenseQuery(todoOptions('123')) await queryClient.ensureQueryData(todosOptions) queryClient.invalidateQueries({ queryKey: todosOptions.queryKey }) ``` ## Advanced Patterns ### Window Focus Refetching ```tsx // Disable globally const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false }, }, }) // Custom focus manager import { focusManager } from '@tanstack/react-query' // For React Native focusManager.setEventListener((handleFocus) => { const subscription = AppState.addEventListener('change', (state) => { handleFocus(state === 'active') }) return () => subscription.remove() }) ``` ### Network Mode ```tsx useQuery({ queryKey: ['todos'], queryFn: fetchTodos, // 'online' (default): only fetch when online // 'always': always fetch (useful for local-first) // 'offlineFirst': try fetch, use cache if offline networkMode: 'offlineFirst', }) ``` ### Query Cancellation ```tsx useQuery({ queryKey: ['todos'], queryFn: async ({ signal }) => { // signal is AbortSignal - automatically cancelled on unmount or key change const res = await fetch('/api/todos', { signal }) return res.json() }, }) // Manual cancellation queryClient.cancelQueries({ queryKey: ['todos'] }) ``` ### Persistence ```tsx import { persistQueryClient } from '@tanstack/react-query-persist-client' import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' const persister = createSyncStoragePersister({ storage: window.localStorage, }) persistQueryClient({ queryClient, persister, maxAge: 1000 * 60 * 60 * 24, // 24 hours }) ``` ## Best Practices 1. **Use `queryOptions` helper** for type-safe, reusable query configurations 2. **Structure query keys hierarchically** for granular invalidation 3. **Set appropriate `staleTime`** - 0 means always refetch on mount (default), increase for less dynamic data 4. **Use `placeholderData`** (not `initialData`) for keeping previous page data during pagination 5. **Prefer `useSuspenseQuery`** when using Suspense boundaries for cleaner component code 6. **Use `enabled`** for dependent queries, not conditional hook calls 7. **Always invalidate after mutations** - don't rely solely on optimistic updates 8. **Cancel queries in `onMutate`** before optimistic updates to prevent race conditions 9. **Use `ensureQueryData`** in route loaders instead of `prefetchQuery` for immediate access 10. **Set `retry: false` in tests** to avoid timeout issues 11. **Don't destructure the query result** if you need to pass it around (breaks reactivity) 12. **Use `select`** for derived data instead of transforming in the component 13. **Keep query functions pure** - they should only fetch, not cause side effects 14. **Use `gcTime: Infinity`** in tests to prevent cache cleanup during assertions ## Common Pitfalls - Using `initialData` when you mean `placeholderData` (initialData counts as "fresh" data) - Not providing `initialPageParam` for infinite queries (required in v5) - Calling hooks conditionally (violates React rules) - Not cancelling queries before optimistic updates (race conditions) - Setting `staleTime` higher than `gcTime` (data gets garbage collected while "fresh") - Forgetting to wrap tests with `QueryClientProvider` - Using same `QueryClient` instance across tests (shared state) - Not awaiting `invalidateQueries` in mutation callbacks when order matters

相关技能

Agent 工作流

Skill Creator

创造有效技能指南。 当用户想创造出新的技能(或更新现有的技能),以专业知识,工作流程,或工具集成来扩展克洛德的能力时,应该使用这种技能.

Agent 工作流

Clawdhub

使用ClawdHub CLI搜索,安装,更新并发布从taladhub.com的代理技能. 需要获取苍蝇上的新技能时使用,将安装的技能同步到最新版本或特定版本,或者发布 npm-instainddhub CLI 的新/更新的技能文件夹.

Agent 工作流

Agent Team Orchestration

管弦乐团多代理团队,任务设定周期,交接协议,审查工作流程. 使用时间: (1)建立2+特派员队伍,具有不同专业,(2)确定任务路线和生命周期(收录框_ spec_建设_审查_完成),(3)在特派员之间制定交接协议,(4)建立审查和质量关口,(5)管理特派员之间的交流和文物共享.

Agent 工作流

Superpowers

Spec-first,TDD,子代理驱动的软件开发工作流程. 当:(1)构建任何新功能或应用——触发脑暴_计划_子代理执行回路,(2)调试出一个bug或测试失败——触发系统性的根起过程,(3)用户说"让我们构建","帮助我计划","我想添加X",或"这个被打破",(4)完成一个功…