GraphQL - Resolver
GraphQL - 解析器
Section titled “GraphQL - 解析器”解析器(Resolvers)是 GraphQL 服务器执行逻辑的核心。它们是负责获取模式(Schema)中特定字段(Field)数据的函数。当一个 GraphQL 查询(Query)到达时,服务器会逐个遍历查询中的字段,执行每个字段对应的解析器函数来生成最终结果。
可以将模式(Schema)视为定义 API 的结构和类型,而解析器(Resolvers)则提供了实现——实际获取或计算数据的代码。
解析器函数签名
Section titled “解析器函数签名”每个解析器函数接收四个参数:
fieldName(parent, args, context, info) { /* ... return data ... */ }让我们详细分解这些参数:
| 参数 | 描述 | 常见用途 |
|---|---|---|
parent(或 **root**) | 父级字段的解析器返回的结果对象。对于 Query 或 Mutation 中的顶层字段,此参数通常是 undefined 或服务器中配置的根值。 | 访问父对象中的数据以解析嵌套字段(例如,从 post 对象获取 userId 以解析 author 字段)。 |
args | 一个包含在 GraphQL 查询中传递给字段的参数的对象。如果字段不接受参数,则此对象将为空。 | 基于 ID 获取特定数据(args.id)、过滤列表(args.filter)、为变更(Mutation)提供输入数据(args.input)。 |
context | 一个在单个 GraphQL 操作(查询/变更)的所有解析器之间共享的对象。它在每次请求时创建一次,可用于传递请求范围的信息。 | 访问身份验证数据(例如,context.user)、数据库连接或模型(context.db)、用于批量处理的数据加载器(context.loaders)。 |
info | 一个包含查询执行状态信息的对象,包括字段名称、返回类型、从根到字段的路径以及查询的抽象语法树(Abstract Syntax Tree, AST)。 | 高级用例,例如实现预读(look-ahead)以根据请求的字段优化数据库查询,或用于复杂的授权逻辑。 |
通常,您不需要所有这四个参数。一种常见的做法是,对于您不打算使用的参数使用下划线(_)表示,例如 greeting(_, __, context),如果您只需要上下文(Context)的话。
解析器映射结构
Section titled “解析器映射结构”解析器通常组织在一个 JavaScript 对象中(通常称为“解析器映射”,resolver map),其结构镜像了 GraphQL 模式(Schema)。
// Example Schema Snippettype Query { greeting: String students: [Student] studentById(id: ID!): Student}
type Student { id: ID! firstName: String lastName: String # 由特定的 Student 解析器解析的字段 fullName: String}
// 对应的解析器映射结构const resolvers = { Query: { // Query.greeting 的解析器 greeting: (parent, args, context, info) => { /* 返回问候字符串 */ },
// Query.students 的解析器 students: (parent, args, context, info) => { /* 返回学生列表 */ },
// Query.studentById 的解析器 studentById: (parent, args, context, info) => { /* 根据 args.id 查找并返回学生 */ }, },
Student: { // Student.fullName 的解析器 // 这会为返回的每个 Student 对象运行 fullName: (parent, args, context, info) => { // 这里的 'parent' 是单个学生对象 return `${parent.firstName} ${parent.lastName}`; }, // id, firstName, lastName 通常不需要解析器,如果父对象 // 由 Query 解析器返回的对象已经具有名称匹配的属性。 // GraphQL 为简单的属性访问提供了默认解析器。 },
// ... 如果定义了 Mutation, Subscription 类型,则对应的解析器};默认解析器: 如果没有明确提供字段的解析器,GraphQL 通常会使用一个默认解析器。这个默认解析器通常会在 parent 对象上查找同名属性,或调用 parent 对象上同名函数。
解析器返回值
Section titled “解析器返回值”解析器可以返回:
- 标量值(Scalar Values):与模式(Schema)类型匹配的字符串(String)、数字(Number)、布尔值(Boolean)等。
- 对象(Objects):普通 JavaScript 对象,其属性对应于模式(Schema)中对象类型(Object type)的字段。
- 数组(Arrays):值或对象的数组,如果模式(Schema)字段类型是列表(List,
[])。 - Promise:解析器通常执行异步操作(例如数据库调用)。返回 Promise 允许 GraphQL 等待操作完成后再继续。
- Null:如果找不到数据或发生错误(并且字段在模式(Schema)中是可空的)。如果一个不可空字段的解析器返回 null,GraphQL 将触发一个错误。
- 错误(Errors):抛出错误(尤其是
GraphQLError)表示解析过程中出现问题。
示例:学生解析器
Section titled “示例:学生解析器”让我们为用于获取学生数据的模式(Schema)创建解析器,包括根据 ID 解析特定学生。
步骤 1:服务器和数据设置
Section titled “步骤 1:服务器和数据设置”假设有一个基本的 Apollo Server 设置和一个模拟数据源(db.js)。
// db.js(模拟数据 - 示例)const students = [ { id: 'S1001', firstName: 'Mohtashim', lastName: 'Mohammad', collegeId: 'col-102' }, { id: 'S1002', firstName: 'Kannan', lastName: 'Sudhakaran', collegeId: 'col-101' },];
export const db = { students: { list: () => students, get: (id) => students.find(s => s.id === id), }};步骤 2:定义模式(Schema)(schema.graphql 或在 server.js 中)
Section titled “步骤 2:定义模式(Schema)(schema.graphql 或在 server.js 中)”#graphqltype Query { greeting: String students: [Student] # 返回 Student 对象列表 studentById(id: ID!): Student # 接收一个非空的 ID,返回单个 Student}
type Student { id: ID! firstName: String lastName: String collegeId: String # 假设这目前只是数据}步骤 3:创建解析器(resolvers.js)
Section titled “步骤 3:创建解析器(resolvers.js)”实现与模式(Schema)对应的解析器函数。
// resolvers.jsimport { db } from './db.js'; // 假设 db.js 使用 export
const resolvers = { Query: { // Query.greeting 的解析器 // 这里除了隐式参数外,不需要其他参数 greeting: () => 'Hello from TutorialsPoint!',
// Query.students 的解析器 // 从我们的模拟数据库获取列表 students: () => { console.log('Resolving: Query.students'); return db.students.list(); },
// Query.studentById 的解析器 // 使用 'args' 参数获取 ID studentById: (parent, args, context, info) => { console.log(`Resolving: Query.studentById with id: ${args.id}`); // 'id' 参数在 args.id 中可用 const student = db.students.get(args.id); if (!student) { // 可选:如果找不到学生,则抛出错误,或返回 null // throw new GraphQLError('未找到学生', { extensions: { code: 'NOT_FOUND' } }); return null; // 如果模式允许 null } return student; }, },
// 这里尚不需要 'Student' 解析器,因为 'id', 'firstName', 'lastName', 'collegeId' // 直接匹配 Query 解析器返回的对象上的属性。 // GraphQL 的默认解析器会处理这种情况。};
export default resolvers;关键点:
greeting解析器仅返回一个静态字符串。students解析器调用db.students.list()来获取数组。studentById解析器通过args.id访问查询中传递的id,并使用它调用db.students.get()。它处理了可能找不到学生的情况。- 我们不需要为
Student.id、Student.firstName等定义解析器,因为students()和studentById()返回的对象已经具有与这些名称完全匹配的属性。在这种情况下,GraphQL 应用默认解析器。
步骤 4:运行和测试
Section titled “步骤 4:运行和测试”将模式(Schema)和解析器与您的 Apollo Server 设置(server.js)集成,并运行服务器(npm start)。
使用 Apollo Sandbox(http://localhost:9000/graphql)进行测试:
# 测试查询 1:获取问候语和所有学生query GetGreetingAndStudents { greeting students { id firstName }}
# 测试查询 2:获取特定学生query GetSpecificStudent { studentById(id: "S1001") { id firstName lastName collegeId }}
# 测试查询 3:获取不存在的学生query GetMissingStudent { studentById(id: "S9999") { id firstName }}观察响应以及解析器输出的控制台日志,以查看它们何时执行。
对于嵌套查询,解析器会链式执行。parent 参数连接了这个链。例如,如果您查询 studentById { college { name } },首先会运行 Query.studentById 解析器,返回一个学生对象。然后,会运行 Student.college 解析器,接收该学生对象作为其 parent 参数。