TanStack Query实现是否合理?求优化方案建议
TanStack Query 实现评估与优化建议
你的代码已经实现了基础的分页+搜索查询功能,但确实还有不少可以挖掘TanStack Query潜力的地方,以下是具体的优化方向:
1. 移除冗余的前端过滤逻辑
你在后端接口已经传入了searchQuery参数做搜索,但前端又对当前页数据做了二次过滤,这会导致两个问题:
- 浪费前端计算资源,后端已经返回了过滤后的结果
- 分页场景下,前端过滤只能覆盖当前页数据,无法得到全量符合条件的结果
直接使用接口返回的data.items即可:
// 移除这段冗余代码 // const filteredItems = // data?.items?.filter((user) => // user.name.toLowerCase().includes(search.toLowerCase()) || // user.email.toLowerCase().includes(search.toLowerCase()) // ) || []; // 直接使用接口返回的结果 const items = data?.items || [];
2. 完善Query的状态处理
当前只用到了data和isLoading,缺少错误状态处理和加载状态的细分(比如背景刷新的isFetching),这会导致用户在请求失败时没有反馈:
const { data, isLoading, isError, error, isFetching // 区分初始加载和后台刷新 } = useQuery({ queryKey: ["users", currentPage, search], queryFn: () => getUsers(currentPage, search), }); // 在组件中添加错误提示 if (isError) { return <div>加载失败: {(error as Error).message}</div>; }
3. 优化分页体验:启用keepPreviousData
切换分页时,默认会清空之前的数据并显示加载状态,启用keepPreviousData可以保留上一页的数据,直到新页数据加载完成,提升流畅度:
const { data, isLoading, isError, error, isFetching, isPreviousData // 标记当前是否使用的是上一页的旧数据 } = useQuery({ queryKey: ["users", currentPage, search], queryFn: () => getUsers(currentPage, search), keepPreviousData: true, // 启用旧数据保留 });
4. 添加请求取消逻辑
使用AbortController在组件卸载或者查询取消时终止未完成的请求,避免无效请求和内存泄漏:
const getUsers = async (page = 1, searchQuery?: string, signal?: AbortSignal): Promise<UserData> => { const url = new URL(`fetch-api`); url.searchParams.append("curPage", page.toString()); if (searchQuery) url.searchParams.append("query", searchQuery); const response = await fetch(url.toString(), { method: "GET", headers: { "Content-Type": "application/json" }, signal, // 传入中止信号 }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return response.json(); }; // 在useQuery中传递signal const { data, isLoading } = useQuery({ queryKey: ["users", currentPage, search], queryFn: ({ signal }) => getUsers(currentPage, search, signal), // 解构获取signal });
5. 配置缓存策略,减少不必要请求
通过staleTime和cacheTime控制数据的新鲜度和缓存时长,避免每次组件挂载都重新请求:
const { data, isLoading } = useQuery({ queryKey: ["users", currentPage, search], queryFn: ({ signal }) => getUsers(currentPage, search, signal), staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜,不发起重复请求 cacheTime: 30 * 60 * 1000, // 数据缓存30分钟后清除 });
6. 同步搜索状态到URL参数
当前搜索状态存在store中,刷新页面后会丢失,将搜索参数同步到URL可以实现页面刷新、分享链接时保留搜索状态:
import { useRouter } from 'next/navigation'; // 以Next.js为例,根据你的框架调整 export default function Page({ searchParams }: PageProps) { const router = useRouter(); const currentPage = Number(searchParams?.curPage) || 1; // 从URL读取初始搜索参数,而不是只依赖store const initialSearch = searchParams?.query || ""; const { search, setSearch } = useUserStore(); // 组件挂载时同步URL参数到store useEffect(() => { if (search !== initialSearch) { setSearch(initialSearch); } }, [initialSearch, search, setSearch]); // 搜索变化时更新URL参数 const handleSearchChange = (newSearch: string) => { setSearch(newSearch); router.push(`/?curPage=1&query=${encodeURIComponent(newSearch)}`, { scroll: false }); }; // 更新queryKey,确保和URL参数一致 const { data, isLoading } = useQuery({ queryKey: ["users", currentPage, search], queryFn: ({ signal }) => getUsers(currentPage, search, signal), }); }
7. 预取下一页数据(可选)
对于分页场景,可以使用prefetchQuery提前加载下一页数据,让用户切换分页时无感知:
import { useQueryClient } from '@tanstack/react-query'; export default function Page({ searchParams }: PageProps) { const queryClient = useQueryClient(); const currentPage = Number(searchParams?.curPage) || 1; const { search } = useUserStore(); // 预取下一页数据 useEffect(() => { if (data?.hasNextPage) { // 假设接口返回hasNextPage标记是否有下一页 queryClient.prefetchQuery({ queryKey: ["users", currentPage + 1, search], queryFn: () => getUsers(currentPage + 1, search), }); } }, [currentPage, search, data?.hasNextPage, queryClient]); // ...其他代码 }
8. 增强类型安全
对currentPage的转换添加有效性校验,避免NaN的情况:
const currentPage = (() => { const pageNum = Number(searchParams?.curPage); return !isNaN(pageNum) && pageNum >= 1 ? pageNum : 1; })();
内容的提问来源于stack exchange,提问作者Lem
相关产品推荐
相关产品推荐

