如何结合graphql-codegen使用@tanstack/react-query的useQueries钩子?
我已经使用graphql-codegen数月,这是一款很棒的工具。我一直将它与@tanstack/react-query的useQuery和useMutation钩子配合使用。
如今我希望将其与useQueries结合使用——我会收到一组动态(数量不确定)的同类型ID,需要通过查询GraphQL API获取对应数据。由于递归问题,无法在服务器端处理,必须在客户端完成,因此需要批量执行单个查询,这也是我想使用useQueries的原因。
看起来graphql-codegen与useQueries之间没有互操作性,是否属实?如果是的话,推荐采用哪种模式来并行运行多个查询?
我的手写实现(未用生成代码):
import { useQueries } from '@tanstack/react-query'; import { gql } from 'graphql-request'; import { graphQLClient } from '../../graphQLClient'; export const useGetSomeStuff = (ids: string[]) => useQueries({ queries: ids.map((id) => ({ queryKey: ['getSomeStuff', id], queryFn: async () => graphQLClient.request( gql` query GetSomeStuff($id: MongoObjectId!) { oneStuff(id: $id) { ...SomeFragment } } `, { id } ), })), });
graphql-codegen生成的代码:
export type GetSomeStuffQueryVariables = Exact<{ id: Scalars['MongoObjectId']; }>; export type GetSomeStuffQuery = { __typename?: 'Query'; // ... 生成的查询字段 }; export const GetSomeStuffDocument = ` query GetSomeStuff($id: MongoObjectId!) { oneStuff(id: $id) { ...SomeFragment } }`; export const useGetSomeStuffQuery = < TData = GetSomeStuffQuery, TError = unknown >( client: GraphQLClient, variables: GetSomeStuffQueryVariables, options?: UseQueryOptions<GetSomeStuffQuery, TError, TData>, headers?: RequestInit['headers'] ) => useQuery<GetSomeStuffQuery, TError, TData>( ['GetSomeStuff', variables], fetcher< GetSomeStuffQuery, GetSomeStuffQueryVariables >(client, GetSomeStuffDocument, variables, headers), options );
首先明确:graphql-codegen的@tanstack/react-query插件默认确实只生成基于useQuery/useMutation的钩子,但这不代表两者没有互操作性——你完全可以利用生成的类型、查询文档和fetcher函数来配合useQueries使用,既保留类型安全,又实现并行查询。
具体实现方式
你可以直接复用生成的GetSomeStuffDocument、GetSomeStuffQueryVariables、GetSomeStuffQuery类型,以及生成代码里用到的fetcher函数(通常这个函数会在生成的文件里或者你配置的utils文件中导出)。重构后的代码如下:
import { useQueries } from '@tanstack/react-query'; import { graphQLClient } from '../../graphQLClient'; // 导入生成的类型、查询文档和fetcher import { GetSomeStuffDocument, GetSomeStuffQueryVariables, GetSomeStuffQuery, fetcher } from './generated/graphql'; export const useGetSomeStuff = (ids: string[]) => useQueries({ queries: ids.map((id) => { const variables: GetSomeStuffQueryVariables = { id }; return { queryKey: ['GetSomeStuff', variables], // 和生成的useQuery保持一致的queryKey queryFn: () => fetcher<GetSomeStuffQuery, GetSomeStuffQueryVariables>( graphQLClient, GetSomeStuffDocument, variables ), // 可以根据需要添加其他useQuery选项,比如enabled、staleTime等 }; }), });
为什么这么做?
- 避免重复手写gql查询语句,减少维护成本
- 完全复用生成的类型,保证变量和返回值的类型安全
- 保持和生成的useGetSomeStuffQuery一致的查询逻辑,避免出现不一致的问题
补充建议
如果后续服务器端支持批量查询(比如提供一个接受ID数组的查询接口),那直接用单个useQuery请求所有数据会更高效,但如果确实只能客户端并行查询,上面的方案就是最优解。
内容的提问来源于stack exchange,提问作者LIIT

