@ldystudio/react-graphql-query
v2.0.0
Published
Definition-driven GraphQL query helpers for React and React Native built on TanStack Query and graphql-request
Maintainers
Readme
react-graphql-query
面向 React 和 React Native 的 definition 驱动 GraphQL 查询辅助库,基于 @tanstack/react-query 与 graphql-request。
特性
- 用一个 definition 绑定
document、parseKey、默认变量、默认查询配置和可选的GraphQLClient - 同时支持手写根类型和
TypedDocumentNode驱动的结果/变量类型推导 - 返回解析后的目标数据,而不是整棵 GraphQL 根对象
- 支持从 GraphQL document 保守推导
parseKey,也支持手动覆盖 - 通过
GraphqlClientProvider为 Hook 场景提供默认 client,也可通过GraphqlQueryProvider一次性组合注入GraphQLClient与QueryClient - 支持 definition 级可选
key,以及getGraphData、setGraphData、invalidateGraphQuery这类缓存辅助函数
安装
npm install @ldystudio/react-graphql-query @tanstack/react-query graphql graphql-request要求:
react >= 18@tanstack/react-query >= 5graphql-request >= 6.1.0
快速开始
import { QueryClient } from "@tanstack/react-query";
import { GraphQLClient, gql } from "graphql-request";
import {
GraphqlQueryProvider,
defineGraphql,
useGraphQuery,
} from "@ldystudio/react-graphql-query";
const client = new GraphQLClient("https://example.com/graphql");
const queryClient = new QueryClient();
interface ProductListRoot {
storefront: {
featuredProducts: {
nodes: Array<{
id: number;
title: string;
}>;
};
};
}
const FEATURED_PRODUCTS = defineGraphql<ProductListRoot>()({
document: gql`
query {
storefront {
featuredProducts {
nodes {
id
title
}
}
}
}
`,
});
function ProductList() {
const query = useGraphQuery(FEATURED_PRODUCTS);
if (query.isPending) {
return null;
}
return query.data?.map((product) => product.title).join(", ");
}
export function App() {
return (
<GraphqlQueryProvider client={client} queryClient={queryClient}>
<ProductList />
</GraphqlQueryProvider>
);
}这个例子里,query.data 会被推导为 Array<{ id: number; title: string }>,因为根类型和 document 都沿着同一条安全路径 storefront.featuredProducts.nodes 收敛。
如果你的应用主要访问同一个 GraphQL endpoint,推荐在组件树外层包一层 GraphqlQueryProvider,这样组件里通常不需要重复传 client,Hook 也能自动读取共享的 QueryClient。
核心 API
defineGraphql
定义一个可复用的 GraphQL definition。
const PRODUCT_DETAIL = defineGraphql<{
catalog: {
product: {
id: number;
title: string;
};
};
}>()({
document: gql`
query ($id: Int!) {
catalog {
product(id: $id) {
id
title
}
}
}
`,
});它也支持直接接收 TypedDocumentNode,这样可以从生成的 GraphQL document 自动推导结果类型和变量类型。
import { ProductDocument } from "./__generated__/graphql";
const PRODUCT_DETAIL = defineGraphql({
document: ProductDocument,
parseKey: "catalog.product",
key: ["catalog", "product-detail"],
});这里的 ProductDocument 必须是一个真实存在的运行时值,通常来自 GraphQL codegen 生成的 document 常量。declare const ... 只能用于类型演示,不能直接放进业务代码执行。
definition 可以包含:
document: 必填,GraphQL 查询或 mutation 文档parseKey: 可选,响应解析路径,例如catalog.productkey: 可选,缓存身份,和parseKey分离variables: 可选,默认变量client: 可选,绑定在 definition 上的GraphQLClient- React Query 选项,例如
staleTime、gcTime、enabled
useGraphQuery
在 React 组件里执行 definition。
const query = useGraphQuery(PRODUCT_DETAIL, {
variables: { id: 7 },
});它内部调用 useQuery(graphQueryOptions(...)),返回标准 UseQueryResult。
如果当前组件树被 GraphqlClientProvider 包裹,且 definition 和 hook options 都没有显式传 client,useGraphQuery 会自动使用 provider 中的 client。
GraphqlQueryProvider
一次性提供 GraphQLClient 和 QueryClient。
<GraphqlQueryProvider client={client} queryClient={queryClient}>
<App />
</GraphqlQueryProvider>这是更推荐的应用级 provider。它内部组合了 QueryClientProvider 和 GraphqlClientProvider。
GraphqlClientProvider
为 Hook 场景提供默认 GraphQLClient。
<GraphqlClientProvider client={client}>
<App />
</GraphqlClientProvider>它会给本库的所有 Hook API 提供默认 GraphQLClient,包括 useGraphQuery、useInfiniteGraphQuery 和 useGraphMutation。
你也可以开启 debugParseKeyHeader,这样 Hook 请求会带上 x-graph-parse-key,方便在 graphql-request 的 requestMiddleware 里打印请求日志。
<GraphqlClientProvider client={client} debugParseKeyHeader>
<App />
</GraphqlClientProvider>graphQuery、graphQueryOptions、graphInfiniteQueryOptions 和 graphMutation 都不会继承 provider client,也不会继承这个调试 header 开关。
useGraphMutation
在 React 组件里执行解析后的 mutation。
const updateProduct = useGraphMutation(UPDATE_PRODUCT);
updateProduct.mutate({
id: 7,
title: "Updated title",
});它内部基于 TanStack Query 的 useMutation,并会自动从 GraphqlQueryProvider 或 QueryClientProvider 读取共享的 QueryClient。
onMutate、onSuccess、onError、onSettled 这些 mutation 回调还会额外收到库层的 GraphMutationContext,其中包含 client、definition 和 queryClient。
graphMutation
在非 Hook 场景直接执行解析后的 mutation。
const product = await graphMutation(UPDATE_PRODUCT, {
client,
variables: {
id: 7,
title: "Updated title",
},
});useInfiniteGraphQuery
在 React 组件里执行解析后的 infinite query。
const query = useInfiniteGraphQuery(PRODUCT_CONNECTION, {
variables: { first: 20 },
initialPageParam: null as string | null,
pageParamToVariables: (pageParam, variables) => ({
...variables,
after: pageParam,
first: variables?.first ?? 20,
}),
getNextPageParam: (lastPage) =>
lastPage.pageInfo.hasNextPage ? lastPage.pageInfo.endCursor : undefined,
});对于 cursor 分页,通常更适合把 parseKey 指向 connection 本身,例如 catalog.products,而不是直接指向 nodes,这样 pageInfo 还在。
graphInfiniteQueryOptions
生成标准 TanStack Query infinite query 配置对象。
const options = graphInfiniteQueryOptions(PRODUCT_CONNECTION, {
variables: { first: 20 },
initialPageParam: null as string | null,
pageParamToVariables: (pageParam, variables) => ({
...variables,
after: pageParam,
first: variables?.first ?? 20,
}),
getNextPageParam: (lastPage) =>
lastPage.pageInfo.hasNextPage ? lastPage.pageInfo.endCursor : undefined,
});graphQuery
在非 Hook 场景直接获取解析后的数据。
import { QueryClient } from "@tanstack/react-query";
import { graphQuery } from "@ldystudio/react-graphql-query";
const queryClient = new QueryClient();
const product = await graphQuery(PRODUCT_DETAIL, {
client,
queryClient,
variables: { id: 7 },
});适合预取、路由加载、SSR 或事件流里取数。
graphQueryOptions
生成标准 TanStack Query 配置对象。
const options = graphQueryOptions(PRODUCT_DETAIL, {
client,
variables: { id: 7 },
});可以配合 useQuery、prefetchQuery 或其他 TanStack Query API 使用。
缓存辅助函数
definition 级缓存 helper 让你直接操作 parse 后的数据,而不用手动回填 GraphQL root shape。
setGraphData 会沿着 parseKey 回写,只更新解析出来的那一段数据,同时保留原始 GraphQL root object 上的 sibling fields。
如果 updater 返回 undefined,这个 helper 不会删除已有缓存;已有缓存会保持不变,而当当前 query key 不存在时,它也不会创建新的缓存条目。
queryKeyOfgetGraphDatasetGraphDatainvalidateGraphQuerycancelGraphQueryremoveGraphQueryresetGraphQuery
乐观更新示例:
const mutation = useGraphMutation(UPDATE_PRODUCT, {
onMutate: async (variables, context) => {
await cancelGraphQuery(context.queryClient, PRODUCT_LIST);
const previous = getGraphData(context.queryClient, PRODUCT_LIST);
setGraphData(context.queryClient, PRODUCT_LIST, undefined, (current) =>
current?.map((item) =>
item.id === variables.id ? { ...item, title: variables.title } : item
)
);
return { previous };
},
onError: (_error, _variables, rollback, context) => {
if (rollback?.previous) {
setGraphData(context.queryClient, PRODUCT_LIST, undefined, rollback.previous);
}
},
onSettled: (_data, _error, _variables, _rollback, context) => {
void invalidateGraphQuery(context.queryClient, PRODUCT_LIST);
},
});parseKey 推导
默认情况下,库会从 document 中自动推导 parseKey。
query {
storefront {
featuredProducts {
nodes {
id
}
}
}
}上面会推导出:
"storefront.featuredProducts.nodes";推导规则是保守的:
- 只有当每一层都只有一个选中字段时才会继续向下
- 运行时解析和类型推导都会停在最后一个无歧义节点
- 遇到不透明包装值、分叉结构或歧义选择时,不会继续收窄
如果你需要显式路径,可以手动传 parseKey:
const PRODUCT_DETAIL = defineGraphql<Root>()({
parseKey: "catalog.product",
document: gql`
query ($id: Int!) {
catalog {
product(id: $id) {
id
title
}
}
}
`,
});显式传入时,以手写值为准。
client 优先级
GraphQLClient 有三个来源:
definition.clientoptions.clientGraphqlClientProvider
优先级如下:
definition.clientoptions.client- provider client
如果三者都没有,会抛错。
queryKey 生成规则
默认情况下,queryKey 由完整的 parseKey 路径和完整的 variables 对象组成。
getGraphQueryKey("catalog.product", { id: 7 });生成结果类似:
["catalog", "product", { id: 7 }];保留完整变量对象可以避免“值序列相同但语义不同”的请求共享同一个缓存键。
如果你希望缓存身份更显式,可以在 definition 上提供 key:
const PRODUCT_LIST = defineGraphql({
document: ProductListDocument,
parseKey: "catalog.products.nodes",
key: ["catalog", "product-list"],
});这会生成类似下面的 query key:
["catalog", "product-list", { first: 20 }];限制
- 当前只支持
graphql-request - 自动
parseKey推导要求 document 只有一个顶层字段 - 结构分叉或语义不明确时,推导会停在最后一个安全节点
debugParseKeyHeader只影响GraphqlClientProvider或GraphqlQueryProvider下的 Hook 请求
License
MIT
