如何避免仅调用嵌套Resolver时触发父Resolver?
我有一个获取帖子评论的GraphQL查询:
post(id:"123") { comments { id, body } }
当前用Node.js+TypeScript实现的Resolver如下:
const resolvers : Resolvers = { Query: { post: (parent, args, info) => { return fetchPost(args.id);} }, Post: { comments: (parent, args, info) => { return fetchComments(parent.id)} } }
问题在于执行该查询时,会先调用postResolver获取完整帖子,但实际上我只需要postId就能拿到评论,postResolver的调用完全冗余。我需要通用解决方案,而非针对这个特定Resolver的临时处理——其他节点也存在类似情况,想了解有没有成熟的通用模式。
我自己目前的方案是重构Schema:
type Query { post(id: ID!): Post } type Post { id: ID! postData: PostData, comments: [Comment!] } type PostData { id: ID! # 原Post的其他详情字段 } type Comment{ id: ID! body: String }
以下是几种成熟的通用模式,可根据场景选择:
1. 新增顶层Query字段直接查询子资源
这是最直观的方案:在Query类型中添加直接查询评论的字段,让前端可以绕过Post直接获取评论,完全避免触发postResolver。
Schema示例:
type Query { post(id: ID!): Post comments(postId: ID!): [Comment!]! # 新增顶层查询 } type Post { id: ID! title: String comments: [Comment!]! } type Comment { id: ID! body: String }
Resolver示例:
const resolvers: Resolvers = { Query: { post: (parent, args) => fetchPost(args.id), comments: (parent, args) => fetchComments(args.postId) // 直接通过postId查询 }, Post: { comments: (parent) => fetchComments(parent.id) } };
前端查询可改成:
comments(postId:"123") { id, body }
2. 在Resolver中解析AST,按需返回数据
利用GraphQL的info参数(包含查询的AST结构),判断当前请求的字段是否需要完整的父对象数据。如果只请求子资源(比如comments),则返回仅包含id的空骨架对象,无需调用父资源的查询接口。
Resolver示例:
import { GraphQLResolveInfo } from 'graphql'; const resolvers: Resolvers = { Query: { post: (parent, args, info: GraphQLResolveInfo) => { // 提取当前请求的所有字段名 const requestedFields = info.fieldNodes[0].selectionSet.selections.map( sel => (sel as any).name.value ); // 如果只请求了comments,返回仅含id的对象,跳过fetchPost if (requestedFields.length === 1 && requestedFields[0] === 'comments') { return { id: args.id }; } // 否则正常获取完整帖子 return fetchPost(args.id); } }, Post: { comments: (parent) => fetchComments(parent.id) } };
这种方案对前端完全透明,不需要修改查询语句,但需要处理AST解析的逻辑,可封装成工具函数复用在所有需要的Resolver中。
3. 拆分Schema字段,延迟父资源查询
就是你尝试的方案:把父对象的详情字段和子资源字段拆分,父Resolver只返回id,只有当查询详情字段时才触发实际的数据库查询。
完善后的Schema示例:
type Query { post(id: ID!): Post } type Post { id: ID! postData: PostData # 存放帖子详情字段 comments: [Comment!]! } type PostData { id: ID! title: String content: String # 其他帖子详情字段 } type Comment { id: ID! body: String }
Resolver示例:
const resolvers: Resolvers = { Query: { post: (parent, args) => { // 仅返回id,不调用fetchPost return { id: args.id }; } }, Post: { postData: (parent) => fetchPost(parent.id), // 只有查询postData时才触发 comments: (parent) => fetchComments(parent.id) } };
当查询post(id:"123") { comments { id, body } }时,postResolver仅返回id,不会触发fetchPost,只有当查询postData字段时才会拉取完整帖子数据。
- 新增顶层Query字段:适合子资源经常被单独查询的场景,前端修改成本低,逻辑清晰。
- AST解析按需返回:对前端透明,无需修改Schema,但需要额外的AST解析逻辑,可封装复用。
- 拆分Schema字段:适合父对象字段较多、子资源频繁独立查询的场景,结构更清晰,但需要调整Schema和前端查询结构。
内容的提问来源于stack exchange,提问作者Mortalus

