React Query:如何为3个子组件统一显示加载Spinner?
方案一:利用React Query的全局查询状态
如果你的useSurveyInfo和useResponses是基于React Query封装的自定义Hook,可以直接借助React Query的全局状态能力,无需修改子组件代码就能实现单一Spinner。
操作步骤:
确认自定义Hook的查询键
确保两个Hook内部使用了可识别的唯一查询键,示例如下:// useSurveyInfo 示例 export const useSurveyInfo = (id) => { return useQuery({ queryKey: ['survey-info', id], // 核心:绑定查询唯一标识 queryFn: () => fetchSurveyInfo(id), }); }; // useResponses 示例 export const useResponses = (id) => { return useQuery({ queryKey: ['responses', id], // 核心:绑定查询唯一标识 queryFn: () => fetchResponses(id), }); };在父组件中监听加载状态
使用useIsFetching钩子监听目标查询的加载状态,判断是否显示Spinner:import { useIsFetching } from '@tanstack/react-query'; const ParentComponent = ({ id }) => { // 监听两个查询的正在加载请求数量 const surveyLoadingCount = useIsFetching({ queryKey: ['survey-info', id] }); const responsesLoadingCount = useIsFetching({ queryKey: ['responses', id] }); const showSpinner = surveyLoadingCount > 0 || responsesLoadingCount > 0; return ( <> {showSpinner && <div className="loading-spinner">加载中...</div>} <ChildComponent1 id={id} /> <ChildComponent2 id={id} /> <ChildComponent3 id={id} /> </> ); };
原理说明:
React Query会全局跟踪所有查询的请求状态,useIsFetching返回匹配指定查询键的正在进行的请求数量。只要有一个请求在加载,就会返回大于0的数值,此时即可触发Spinner显示。同时React Query的缓存机制会避免多个组件调用同一查询时重复发起请求,不会影响性能。
方案二:自定义Context管理加载状态
如果你的自定义Hook是自行实现的(不依赖React Query),可以通过React Context统一管理所有子组件的加载状态,彻底避免prop drilling。
操作步骤:
创建加载状态Context
import { createContext, useContext, useState } from 'react'; const LoadingContext = createContext(); export const LoadingProvider = ({ children }) => { // 用计数器跟踪加载中的请求数量 const [loadingCount, setLoadingCount] = useState(0); const startLoading = () => setLoadingCount(prev => prev + 1); const endLoading = () => setLoadingCount(prev => Math.max(prev - 1, 0)); return ( <LoadingContext.Provider value={{ startLoading, endLoading, isLoading: loadingCount > 0 }} > {children} </LoadingContext.Provider> ); }; // 自定义Hook简化Context调用 export const useLoading = () => useContext(LoadingContext);修改自定义Hook,同步全局加载状态
在useSurveyInfo和useResponses中,调用Context方法更新全局加载状态:export const useSurveyInfo = (id) => { const { startLoading, endLoading } = useLoading(); const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); useEffect(() => { const fetchData = async () => { setIsLoading(true); startLoading(); try { const res = await fetch(`/api/survey/${id}`); const result = await res.json(); setData(result); setIsError(false); } catch (err) { setIsError(true); } finally { setIsLoading(false); endLoading(); } }; fetchData(); }, [id, startLoading, endLoading]); return { data, isLoading, isError }; };按照相同逻辑修改
useResponsesHook。在父组件中使用Spinner
确保父组件被LoadingProvider包裹(可在根组件统一包裹),然后获取全局加载状态:import { useLoading } from './LoadingContext'; const ParentComponent = ({ id }) => { const { isLoading } = useLoading(); return ( <> {isLoading && <div className="loading-spinner">加载中...</div>} <ChildComponent1 id={id} /> <ChildComponent2 id={id} /> <ChildComponent3 id={id} /> </> ); };
原理说明:
通过Context维护一个全局加载计数器,每个子组件发起请求时递增计数器,请求结束后递减计数器。只要计数器大于0,就显示Spinner。这种方式完全自定义,不依赖第三方库。
内容的提问来源于stack exchange,提问作者psych0groov3

