React+TypeScript中基于React-Query的Query Provider开发需求
React+TypeScript 实现带状态渲染的Query Provider组件
1. 定义上下文与类型
先创建用于传递Query数据的上下文,同时提供类型保障:
import { ReactNode, createContext, useContext } from 'react'; import { UseQueryResult } from '@tanstack/react-query'; // 提取useQuery返回的核心属性作为上下文类型 type QueryContextValue<TData = unknown> = Pick< UseQueryResult<TData>, 'data' | 'refetch' | 'isFetching' >; // 创建上下文,初始值设为null const QueryContext = createContext<QueryContextValue | null>(null); // 子组件用这个钩子获取上下文数据 export function useQueryData<TData = unknown>() { const context = useContext(QueryContext); if (!context) { throw new Error('useQueryData必须在Query组件内部使用'); } return context as QueryContextValue<TData>; }
2. 实现状态插槽组件
定义三个插槽组件,用于标记不同状态下要渲染的内容:
type QuerySlotProps = { children: ReactNode; }; export function QuerySuccess({ children }: QuerySlotProps) { return <>{children}</>; } export function QueryError({ children }: QuerySlotProps) { return <>{children}</>; } export function QueryLoading({ children }: QuerySlotProps) { return <>{children}</>; }
3. 核心Query Provider组件
接收useQuery返回的所有参数,自动根据状态渲染对应插槽,并通过上下文传递数据:
type QueryProps<TData = unknown> = UseQueryResult<TData> & { children: ReactNode; }; export function Query<TData = unknown>({ data, refetch, isFetching, isLoading, isError, error, children, }: QueryProps<TData>) { // 从子元素中筛选对应状态的组件 const childArray = React.Children.toArray(children); const successChild = childArray.find(child => React.isValidElement(child) && child.type === QuerySuccess ); const errorChild = childArray.find(child => React.isValidElement(child) && child.type === QueryError ); const loadingChild = childArray.find(child => React.isValidElement(child) && child.type === QueryLoading ); // 按状态分支渲染 if (isLoading) { return loadingChild ?? <div>加载中...</div>; } if (isError) { return errorChild ?? <div>加载失败: {(error as Error).message}</div>; } // 成功状态下注入上下文,渲染成功组件 return ( <QueryContext.Provider value={{ data, refetch, isFetching }}> {successChild ?? null} </QueryContext.Provider> ); }
4. 使用示例
按照你给出的结构使用,子组件通过useQueryData获取数据:
// 假设这是你的自定义useQuery钩子 function useGetDashboardInfo() { return useQuery({ queryKey: ['dashboardInfo'], queryFn: () => fetch('/api/dashboard').then(res => res.json()), }); } export function DashboardView() { const queryData = useGetDashboardInfo(); return ( <Query {...queryData}> <QueryLoading> <div>仪表盘加载中...</div> </QueryLoading> <QuerySuccess> <DashboardComponent /> </QuerySuccess> <QueryError> <div>仪表盘加载失败,请稍后重试</div> </QueryError> </Query> ); } // 子组件中获取数据和刷新能力 function DashboardComponent() { const { data, refetch, isFetching } = useQueryData<{ stats: { todayViews: number; totalUsers: number } }>(); return ( <div> <h1>仪表盘</h1> {isFetching && <span>数据刷新中...</span>} <button onClick={() => refetch()}>刷新数据</button> {data && ( <div> <p>今日访问量: {data.stats.todayViews}</p> <p>总用户数: {data.stats.totalUsers}</p> </div> )} </div> ); }
关键特性
- 类型安全:通过泛型确保数据类型匹配,避免类型错误
- 灵活定制:可自定义加载/错误/成功状态的展示内容,也可使用默认渲染
- 无props drilling:子组件直接通过钩子获取数据和刷新方法,无需层层传递
内容的提问来源于stack exchange,提问作者szescian6
相关产品推荐
相关产品推荐

