关于 state-management
面向使用 TanStack Query v5 和 Zustand v5 的 React 开发者与编码 Agent 的参考技能,涵盖服务端和客户端状态、缓存、变更及安全持久化。
主要功能
- 01TanStack Query v5 模式
- 02Zustand v5 状态仓库模式
- 03缓存与失效指导
- 04变更与乐观更新模式
- 05DevTools 与调试指导
- 06敏感状态持久化安全规范
适用场景
- 01获取并缓存 API 数据
- 02构建客户端状态仓库
- 03实现变更与乐观更新
- 04组合服务端状态与界面状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ asyrafhussin/ agent-skills --skill state-management安装后在 Claude Code 中输入/state-management确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonREADME.mdrules/_sections.md_template.mdrq-cache-time.mdrq-dependent-queries.mdrq-enabled-option.mdrq-infinite-queries.mdrq-initial-data.mdrq-mutation-callbacks.mdrq-mutation-setup.mdrq-mutation-side-effects.mdrq-mutation-variables.mdrq-optimistic-updates.mdrq-paginated-queries.mdrq-parallel-queries.mdrq-placeholder-data.mdrq-prefetching.mdrq-query-cancellation.mdrq-query-functions.mdrq-query-invalidation.mdrq-query-keys.mdrq-refetch-options.mdrq-retry-logic.mdrq-select-transform.mdrq-stale-time.mdrq-suspense-mode.mdrq-usequery.mdzs-create-store.md详细介绍
使用 React Query + Zustand 进行状态管理
版本 1.1.0 | TanStack Query v5 | Zustand v5 | 2026 年 3 月
注意: 本文档为使用 TanStack Query v5 和 Zustand v5 的 AI Agent 与大模型提供全面的模式指南。所有示例均已针对 v5 API 验证。 针对自动化重构、代码生成和状态管理最佳实践进行了优化。
v5 破坏性变更(快速参考)
TanStack Query v5:
cacheTime→gcTimekeepPreviousData选项 →placeholderData: keepPreviousData(导入的辅助函数)isPreviousData→isPlaceholderDatauseQuery中的onSuccess/onError/onSettled已移除 — 在useMutation中仍然有效useQuery中的suspense: true已移除 → 改用useSuspenseQuery
Zustand v5:
- 已移除作为第二个参数的
shallow→ 使用zustand/shallow中的useShallow - 返回新引用的选择器需要使用
useShallow,以避免无限循环
安全性:持久化中间件
切勿将身份验证令牌、密码或机密信息持久化到 localStorage/sessionStorage。 使用
partialize仅持久化非敏感状态。通过 HttpOnly Cookie 管理令牌。
本指南介绍服务器状态(React Query)和客户端状态(Zustand)的全面模式,包含 26 条以上关于高效数据获取和状态管理的规则。
适用场景
在以下情况下参考这些指南:
- 从 API 获取数据
- 管理服务器状态和缓存
- 处理变更和乐观更新
- 创建客户端存储
- 将 React Query 与 Zustand 结合使用
按优先级划分的规则类别
| 优先级 | 类别 | 影响 | 前缀 |
|---|---|---|---|
| 1 | React Query 基础知识 | 严重 | rq- |
| 2 | Zustand 存储模式 | 严重 | zs- |
| 3 | 缓存与失效 | 高 | cache- |
| 4 | 变更与更新 | 高 | mut- |
| 5 | 乐观更新 | 中 | opt- |
| 6 | 开发工具与调试 | 中 | dev- |
| 7 | 高级模式 | 低 | adv- |
快速参考
1. React Query 基础知识(严重)
rq-setup- 设置 QueryClient 和 Providerrq-usequery- useQuery 基本模式rq-querykeys- 查询键组织rq-loading-error- 处理加载和错误状态rq-enabled- 条件查询
2. Zustand 存储模式(严重)
zs-create-store- 创建基本存储zs-typescript- TypeScript 存储模式zs-selectors- 高效选择器zs-actions- 操作模式zs-persist- 将状态持久化到存储中
3. 缓存与失效(高)
cache-stale-time- 配置过期时间cache-gc-time- 配置垃圾回收时间cache-invalidation- 使查询失效cache-prefetch- 预取数据cache-initial-data- 设置初始数据
4. 变更与更新(高)
mut-usemutation- useMutation 基础用法mut-callbacks- onSuccess、onError 回调mut-invalidate- 变更后使查询失效mut-update-cache- 直接更新缓存
5. 乐观更新(中)
opt-basic- 乐观更新基础用法opt-rollback- 出错时回滚opt-variables- 使用变更变量
6. 开发工具与调试(中)
dev-react-query- React Query DevToolsdev-zustand- Zustand DevToolsdev-debugging- 调试策略
7. 高级模式(低)
adv-infinite-queries- 无限滚动adv-parallel-queries- 并行请求adv-dependent-queries- 依赖查询adv-query-zustand- 将 RQ 与 Zustand 结合使用
React Query 模式
设置
// lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)
retry: 1,
refetchOnWindowFocus: false,
},
},
})
// App.tsx
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { queryClient } from './lib/queryClient'
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
查询键工厂
// lib/queryKeys.ts
export const queryKeys = {
// All posts
posts: {
all: ['posts'] as const,
lists: () => [...queryKeys.posts.all, 'list'] as const,
list: (filters: PostFilters) =>
[...queryKeys.posts.lists(), filters] as const,
details: () => [...queryKeys.posts.all, 'detail'] as const,
detail: (id: number) => [...queryKeys.posts.details(), id] as const,
},
// All users
users: {
all: ['users'] as const,
detail: (id: number) => [...queryKeys.users.all, id] as const,
posts: (userId: number) => [...queryKeys.users.all, userId, 'posts'] as const,
},
}
useQuery Hook
// hooks/usePosts.ts
import { useQuery } from '@tanstack/react-query'
import { queryKeys } from '@/lib/queryKeys'
import { fetchPosts, fetchPost } from '@/api/posts'
export function usePosts(filters?: PostFilters) {
return useQuery({
queryKey: queryKeys.posts.list(filters ?? {}),
queryFn: () => fetchPosts(filters),
})
}
export function usePost(id: number) {
return useQuery({
queryKey: queryKeys.posts.detail(id),
queryFn: () => fetchPost(id),
enabled: !!id, // Only run if id exists
})
}
// Usage in component
function PostList() {
const { data: posts, isLoading, error } = usePosts()
if (isLoading) return <Spinner />
if (error) return <Error message={error.message} />
return (
<ul>
{posts?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
useMutation Hook
// hooks/useCreatePost.ts
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { queryKeys } from '@/lib/queryKeys'
import { createPost } from '@/api/posts'
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: createPost,
onSuccess: (newPost) => {
// Invalidate and refetch posts list
queryClient.invalidateQueries({
queryKey: queryKeys.posts.lists(),
})
},
onError: (error) => {
console.error('Failed to create post:', error)
},
})
}
// Usage
function CreatePostForm() {
const { mutate, isPending } = useCreatePost()
const handleSubmit = (data: CreatePostData) => {
mutate(data)
}
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button disabled={isPending}>
{isPending ? 'Creating...' : 'Create'}
</button>
</form>
)
}
乐观更新
export function useUpdatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updatePost,
onMutate: async (updatedPost) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({
queryKey: queryKeys.posts.detail(updatedPost.id),
})
// Snapshot previous value
const previousPost = queryClient.getQueryData(
queryKeys.posts.detail(updatedPost.id)
)
// Optimistically update
queryClient.setQueryData(
queryKeys.posts.detail(updatedPost.id),
updatedPost
)
return { previousPost }
},
onError: (err, updatedPost, context) => {
// Rollback on error
queryClient.setQueryData(
queryKeys.posts.detail(updatedPost.id),
context?.previousPost
)
},
onSettled: (data, error, variables) => {
// Refetch after settle
queryClient.invalidateQueries({
queryKey: queryKeys.posts.detail(variables.id),
})
},
})
}
Zustand 模式
基本存储
// stores/useCounterStore.ts
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
// Usage
function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
使用 TypeScript 和中间件的存储
// stores/useAuthStore.ts
import { create } from 'zustand'
import { persist, devtools } from 'zustand/middleware'
interface User {
id: number
name: string
email: string
}
interface AuthState {
user: User | null
isAuthenticated: boolean
login: (user: User) => void
logout: () => void
}
// ✅ Never persist tokens to localStorage — use HttpOnly cookies server-side
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set) => ({
user: null,
isAuthenticated: false,
login: (user) =>
set({
user,
isAuthenticated: true,
}),
logout: () =>
set({
user: null,
isAuthenticated: false,
}),
}),
{
name: 'auth-storage',
// Only persist display info and auth flag — tokens must NOT be included
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
}
)
)
)
用于提升性能的选择器
// Use selectors to prevent unnecessary re-renders
function UserName() {
// Only re-renders when user.name changes
const name = useAuthStore((state) => state.user?.name)
return <span>{name}</span>
}
// Multiple selectors
function UserInfo() {
const user = useAuthStore((state) => state.user)
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
if (!isAuthenticated) return <LoginButton />
return <span>{user?.name}</span>
}
结合使用 React Query + Zustand
// Server state: React Query (what comes from API)
const { data: posts } = usePosts()
// Client state: Zustand (UI state)
const { selectedPostId, selectPost } = useUIStore()
// Use together
const selectedPost = posts?.find((p) => p.id === selectedPostId)
使用方法
阅读各规则文件,了解详细说明和代码示例:
rules/rq-usequery.md
rules/rq-query-keys.md
rules/rq-mutation-setup.md
rules/rq-optimistic-updates.md
rules/zs-create-store.md
rules/zs-persist.md
rules/rq-query-invalidation.md
rules/rq-prefetching.md
参考资料
React Query(TanStack Query)
Zustand
许可证
此技能按原样提供,仅用于教育和开发目的。React Query 由 TanStack 采用 MIT 许可证授权。Zustand 由 Poimandres(pmnd.rs)采用 MIT 许可证授权。
评论
支持 Markdown