React Query随机重取与重渲染异常问题求助
项目使用React Query,此前运行正常,近期出现API随机重取的异常,且发现获取通知后页面内所有API都会被重取。
代码片段
通知钩子代码
export const GET_NOTIFICATIONS = "GET_NOTIFICATIONS"; const useNotification = (autoFetch: boolean, query: any) => { const REFETCH_EACH = 7000; const { httpClient } = useAxios(); const getNotifications = async () => httpClient?.get<any>(`${NOTIFICATIONS_ENDPOINT}/search`, query); return useQuery([GET_NOTIFICATIONS], () => getNotifications(), { enabled: autoFetch && httpClient !== undefined, refetchInterval: autoFetch ? REFETCH_EACH : false }); };
全局上下文提供者代码
interface Props { children: ReactNode; } const GlobalContextProvider = (props: Props) => { const { state, setState } = useSharedContext(); const { data: referentials, isSuccess, isFetching: referentialsFetching } = useReferentials(); const { data: notifications, isSuccess: notificationsSuccess } = useNotification(true, { status: notificationStatus.PENDING.status, page: 0, size: 100 }); const { data: rbac, isSuccess: rbacSuccess, isFetching: rbacLoading } = useRoleBased(); useEffect(() => { if (isSuccess && setState) { setState((prev) => ({ ...prev, referentials })); } }, [referentials, isSuccess, setState]); useEffect(() => { if (notificationsSuccess && setState) { setState((prev) => ({ ...prev, notifications })); } }, [notifications, notificationsSuccess]); useEffect(() => { if (rbacSuccess && setState) { setState((prev) => ({ ...prev, rbac, })); } }, [rbac, rbacSuccess, setState]); if (rbacLoading || referentialsFetching) { return <FullPageLoader title="Chargement en cours..." />; } return <GlobalContext.Provider value={{ state, setState }}>{props.children}</GlobalContext.Provider>; }; export default GlobalContextProvider;
被随机重取的API示例代码
const usePreRegulationKpi = () => { const { httpClient } = useAxios(); const getPreRegulationKpi = async () => httpClient?.get<any>(`${INTERMEDIATE_ENDPOINT}/kpi`); return useQuery([GET_REGULATION_KPI], () => getPreRegulationKpi(), { enabled: false }); }; // 调用示例: const { refetch: refetchPreRegulationKpi, isFetching: regulationKPIsLoading, isSuccess: regulationKPIsSuccess, isError: regulationKPIsError, data: regulationKPIsData } = usePreRegulationKpi();
React Query全局配置代码
import { QueryClient, QueryClientConfig } from "@tanstack/react-query"; const queryClientConfig: QueryClientConfig = { defaultOptions: { queries: { refetchOnWindowFocus: false, retry: false } } }; export const queryClient = new QueryClient(queryClientConfig);
问题原因分析
1. 全局上下文Provider的条件渲染触发子组件卸载/重挂载
GlobalContextProvider在rbacLoading或referentialsFetching为true时返回FullPageLoader,直接替换子组件渲染,导致子组件被卸载。当通知查询完成更新全局state后,若useRoleBased或useReferentials的查询依赖state值并触发重取,会使rbacLoading/referentialsFetching再次变为true,触发Loader渲染;查询完成后子组件重新挂载,内部所有useQuery会重新初始化,若组件挂载时存在自动触发的查询(或useEffect中调用refetch),就会导致API批量重取。
2. 通知查询的Query Key未包含动态参数
useNotification的query key仅使用[GET_NOTIFICATIONS],未将传入的query参数纳入其中。若query参数发生变化,React Query会判定为同一查询,可能引发缓存混乱,间接触发其他查询的异常重取。
3. 全局state更新导致子组件频繁重渲染
通知更新触发全局state修改后,所有消费GlobalContext的子组件会重渲染。若子组件中的useQuery依赖state值(如query key包含state),或在useEffect中以state为依赖调用refetch,都会触发对应API的重取。
解决方案
1. 修复全局Provider的条件渲染逻辑
避免通过条件渲染卸载子组件,改为在Provider内部控制Loader的显示:
const GlobalContextProvider = (props: Props) => { // ... 保留原有逻辑 return ( <GlobalContext.Provider value={{ state, setState }}> {rbacLoading || referentialsFetching ? ( <FullPageLoader title="Chargement en cours..." /> ) : ( props.children )} </GlobalContext.Provider> ); };
此方式下子组件不会被卸载,仅被隐藏,避免重挂载导致的查询重执行。
2. 完善通知查询的Query Key
将动态参数query加入query key,确保不同参数对应独立的缓存条目:
return useQuery([GET_NOTIFICATIONS, query], () => getNotifications(), { enabled: autoFetch && httpClient !== undefined, refetchInterval: autoFetch ? REFETCH_EACH : false });
3. 优化子组件对全局Context的依赖
- 避免在useEffect中以全局state为依赖调用
refetch,除非业务确有需要; - 若必须依赖,使用
useCallback包装refetch方法,减少不必要的触发; - 确保useQuery的query key仅包含必要依赖,避免因无关state变化触发重取。
4. 排查useRoleBased和useReferentials的查询逻辑
检查这两个查询是否依赖全局state,若存在不必要的依赖,调整query key或移除依赖;若确需依赖state,确保查询重取不会触发子组件卸载。
内容的提问来源于stack exchange,提问作者Oussama Abouzid

