name: react-query description: TanStack Query(React Query)模式,用于服务器状态管理、缓存、变更、乐观更新和无限查询。 allowed-tools: Read, Write, Edit, Bash, Glob, Grep
React Query 技能
为在 React 应用程序中实现 TanStack Query(React Query)进行服务器状态管理提供专家级协助。
能力
- 使用最优默认值配置 QueryClient
- 使用缓存策略实现查询
- 处理带有乐观更新的变更
- 为分页设置无限查询
- 管理查询失效和预取
- 与身份验证和错误处理集成
使用场景
在以下情况时调用此技能:
- 获取和缓存服务器数据
- 处理带有回滚的变更
- 实现无限滚动或分页
- 为导航预取数据
- 同步服务器状态
输入参数
| 参数 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| endpoint | string | 是 | 要查询的 API 端点 |
| queryKey | array | 是 | 唯一的查询键 |
| staleTime | number | 否 | 数据变为陈旧的时间(毫秒) |
| cacheTime | number | 否 | 在缓存中保留的时间(毫秒) |
| optimisticUpdate | boolean | 否 | 启用乐观更新 |
配置示例
{
"endpoint": "/api/users",
"queryKey": ["users"],
"staleTime": 300000,
"cacheTime": 600000,
"optimisticUpdate": true
}
生成的模式
Query Client 设置
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 分钟
gcTime: 1000 * 60 * 30, // 30 分钟(原 cacheTime)
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
},
},
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
基础查询钩子
import { useQuery } from '@tanstack/react-query';
interface User {
id: string;
name: string;
email: string;
}
async function fetchUsers(): Promise<User[]> {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('获取用户失败');
}
return response.json();
}
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
}
export function useUser(userId: string) {
return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId,
});
}
带有乐观更新的变更
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface UpdateUserDto {
id: string;
name?: string;
email?: string;
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: UpdateUserDto) => {
const response = await fetch(`/api/users/${data.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('更新用户失败');
return response.json();
},
onMutate: async (newData) => {
// 取消正在进行的重新获取
await queryClient.cancelQueries({ queryKey: ['users', newData.id] });
// 快照先前值
const previousUser = queryClient.getQueryData(['users', newData.id]);
// 乐观更新
queryClient.setQueryData(['users', newData.id], (old: User) => ({
...old,
...newData,
}));
return { previousUser };
},
onError: (err, newData, context) => {
// 出错时回滚
if (context?.previousUser) {
queryClient.setQueryData(['users', newData.id], context.previousUser);
}
},
onSettled: (data, error, variables) => {
// 重新获取以确保一致性
queryClient.invalidateQueries({ queryKey: ['users', variables.id] });
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
无限查询
import { useInfiniteQuery } from '@tanstack/react-query';
interface Page {
data: User[];
nextCursor?: string;
}
async function fetchUsersPage({ pageParam }: { pageParam?: string }): Promise<Page> {
const url = pageParam
? `/api/users?cursor=${pageParam}`
: '/api/users';
const response = await fetch(url);
return response.json();
}
export function useInfiniteUsers() {
return useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: fetchUsersPage,
initialPageParam: undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
getPreviousPageParam: (firstPage) => firstPage.previousCursor,
});
}
// 在组件中使用
function UserList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteUsers();
return (
<div>
{data?.pages.map((page, i) => (
<React.Fragment key={i}>
{page.data.map((user) => (
<UserCard key={user.id} user={user} />
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '加载中...' : hasNextPage ? '加载更多' : '没有更多了'}
</button>
</div>
);
}
预取
import { useQueryClient } from '@tanstack/react-query';
function UserLink({ userId }: { userId: string }) {
const queryClient = useQueryClient();
const prefetchUser = () => {
queryClient.prefetchQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
staleTime: 1000 * 60 * 5,
});
};
return (
<Link
to={`/users/${userId}`}
onMouseEnter={prefetchUser}
onFocus={prefetchUser}
>
查看用户
</Link>
);
}
查询键工厂
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: Filters) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
// 使用
useQuery({ queryKey: userKeys.detail(userId), ... });
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
最佳实践
- 使用查询键工厂以确保一致性
- 为数据设置适当的陈旧时间
- 实现乐观更新以提升用户体验
- 策略性地使用 placeholderData 或 initialData
- 将服务器状态与客户端状态分离
目标流程
- react-application-development
- nextjs-full-stack
- data-fetching-setup
- performance-optimization