如何自定义React-Query Hook为所有查询自动附加AccessToken?
问题描述
我花了8小时研究这个问题却陷入困境。本以为创建useAuthenticatedQuery Hook,在queryFn中自动传入AccessToken会很简单,但TypeScript却带来极大困扰。
目前我在所有页面中使用如下默认查询:
const {accessToken} = useAuth() const subscriptionQuery = useQuery({ queryKey: ["subscription", accessToken], queryFn: () => { return api.getSubscription(accessToken); }, });
我不想每次手动将AccessToken加入queryKey,也不想每次都调用useAuth Hook获取AccessToken。
以下是我接近目标的代码,但TypeScript报错严重:
const useAuthenticatedQuery = (options: UseQueryOptions) => { const { accessToken } = useAuth(); return useQuery({ ...options, queryKey: [options.queryKey, accessToken], queryFn: (context) => { return options && options.queryFn && options.queryFn({ ...context, accessToken }); }, }); };
将AccessToken加入queryKey时TypeScript抛出如下错误:
No overload matches this call. The last overload gave the following error. Argument of type '{ queryKey: (string | QueryKey | undefined)[]; queryFn: (context: any) => unknown; context?: Context<QueryClient | undefined> | undefined; enabled?: boolean | undefined; ... 31 more ...; meta?: QueryMeta | undefined; }' is not assignable to parameter of type 'QueryKey'. Object literal may only specify known properties, and 'queryKey' does not exist in type 'readonly unknown[]'.ts(2769)
我目前最接近的实现如下:
const useAuthenticatedQuery = < TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey >( key: TQueryKey, fn: (accessToken: string) => QueryFunction<TQueryFnData, TQueryKey>, options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> = {} ) => { const { accessToken } = useAuth(); return useQuery(key, fn(accessToken), { ...options, onError: (error) => { options.onError?.(error); }, }); };
但调用时写法繁琐,还没解决AccessToken加入queryKey的问题:
const subscriptionQuery = useAuthenticatedQuery( ["subscription"], () => (accessToken) => api.getSubscription(accessToken), {} )
需要更优的实现方式。
解决方案
我们可以通过泛型约束严格对齐React Query的类型,同时自动注入accessToken到queryKey和queryFn中,保持调用简洁。
最终实现代码
import { useQuery, UseQueryOptions, QueryKey, QueryFunctionContext } from '@tanstack/react-query'; import { useAuth } from './your-auth-module-path'; type AuthenticatedQueryFn<TQueryFnData, TQueryKey extends QueryKey> = ( context: QueryFunctionContext<TQueryKey>, accessToken: string ) => TQueryFnData | Promise<TQueryFnData>; const useAuthenticatedQuery = < TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey >( options: Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & { queryKey: TQueryKey; queryFn: AuthenticatedQueryFn<TQueryFnData, TQueryKey>; } ) => { const { accessToken } = useAuth(); return useQuery({ ...options, // 自动将accessToken追加到queryKey末尾 queryKey: [...options.queryKey, accessToken] as TQueryKey extends (infer U)[] ? [...U, string] : never, // 注入accessToken到queryFn参数 queryFn: (context) => options.queryFn(context, accessToken), }); };
调用方式
现在调用变得简洁,无需手动处理accessToken:
const subscriptionQuery = useAuthenticatedQuery({ queryKey: ["subscription"], queryFn: (_, accessToken) => api.getSubscription(accessToken), });
关键说明
- 泛型约束:通过
Omit去掉原UseQueryOptions中的queryFn,替换为自定义的包含accessToken参数的AuthenticatedQueryFn,确保类型安全。 - queryKey自动拼接:利用类型断言将
accessToken追加到传入的queryKey后,同时保持类型兼容性。 - 自动注入accessToken:在内部调用
useAuth获取accessToken,并传递给自定义的queryFn,调用方无需重复获取。
这样既解决了手动添加accessToken到queryKey的问题,也避免了重复调用useAuth,同时满足TypeScript的类型检查要求。
内容的提问来源于stack exchange,提问作者user20715205
相关产品推荐
相关产品推荐

