为何useQuery为每个Observer触发?如何按需展示Toast?
React Query 全局回调过滤与类型处理问题
现有实现
我使用useQuery的onSuccess回调验证API调用是否返回数据,若未返回数据则展示“未找到结果”的Toast。
遇到的问题
Toast会为每个Observer重复渲染,这是钩子为每个Observer执行的预期行为,但不符合需求。
核心需求
通过全局回调避免重复渲染Toast,需按请求的queryKey过滤,仅当queryKey为“search”且无返回数据时展示Toast,现有代码框架如下:
const queryClient = new QueryClient({ queryCache: new QueryCache({ onSuccess: (error) => // how to filter here the request to show the toast // according to the queryKey and if no data is retrieved from the backend }), })
其他问题
- 还有哪些方法可避免该行为(避免在每个Observer中执行代码)?
- 使用Axios作为请求客户端时,如何处理错误或成功回调中的TS类型?
解决方案
1. 全局QueryCache的onSuccess回调过滤实现
首先纠正一个小问题:QueryCache的onSuccess回调参数是data、query,不是error。你可以通过query.queryKey判断目标请求,同时检查返回数据是否为空:
const queryClient = new QueryClient({ queryCache: new QueryCache({ onSuccess: (data, query) => { // 过滤queryKey为"search"的请求,同时判断数据是否为空 if (query.queryKey[0] === "search" && (!data || Object.keys(data).length === 0)) { // 展示"未找到结果"的Toast showToast("未找到结果"); } }, }), });
2. 避免重复执行的其他方法
- 封装自定义Hook:写一个专属的
useSearchQuery,把Toast逻辑放在这个Hook里,确保每个搜索请求只执行一次逻辑,避免多个Observer重复触发:const useSearchQuery = (searchParams) => { return useQuery({ queryKey: ["search", searchParams], queryFn: () => axios.get("/api/search", { params: searchParams }), onSuccess: (data) => { if (!data || Object.keys(data).length === 0) { showToast("未找到结果"); } }, // 设置staleTime减少重复请求触发回调的次数 staleTime: 5000, }); }; - 手动触发全局检查:使用
queryClient.invalidateQueries时,结合自定义逻辑只在搜索请求完成后触发一次Toast,不依赖Observer回调。 - 全局Context管理状态:把搜索结果的空状态存入全局Context,只在状态变化时触发一次Toast,避免重复渲染。
3. Axios结合TypeScript的类型处理
成功回调类型定义
为Axios响应定义接口,明确返回数据结构,让TS自动推断类型:
interface SearchResponse { results: Array<{ id: string; name: string }>; total: number; } // 在queryFn中指定响应类型 const queryFn = async () => { const response = await axios.get<SearchResponse>("/api/search"); return response.data; };
此时onSuccess中的data会自动匹配SearchResponse类型,无需额外断言。
错误回调类型处理
用AxiosError捕获错误,结合自定义后端错误结构:
import { AxiosError } from "axios"; // 自定义后端返回的错误格式 interface ApiError { message: string; code: number; } // 在useQuery的onError中处理 onError: (error) => { const axiosError = error as AxiosError<ApiError>; if (axiosError.response?.data) { console.error(axiosError.response.data.message); } };
内容的提问来源于stack exchange,提问作者Cristian Flórez
相关产品推荐
相关产品推荐

