Skip to content

GraphQL - Query

在 GraphQL 中,查询(Query)是用于从服务器读取或获取数据的操作。它类似于 REST 中的 GET 请求。客户端构造查询时,精确指定他们需要的字段(Field),可能包括嵌套的相关数据以及用于过滤或标识特定对象的参数(Argument)。

服务器处理查询,执行必要的解析器(Resolvers),并返回一个与请求字段结构精确匹配的 JSON 响应。

最简单的查询是请求模式(Schema)的 Query 类型中定义的字段:

# 语法 1:匿名查询(简写)
{
fieldName
}
# 语法 2:命名查询(推荐用于清晰度和多个操作)
query OperationName {
fieldName
}

示例(假设 Query 类型中存在 greeting 字段):

# 匿名查询
{
greeting
}
# 命名查询
query GetGreeting {
greeting
}

尽管对于单个查询来说,query 关键字和操作名称是可选的,但显式命名您的操作是一种最佳实践,尤其是在处理变量(Variables)或在一次请求中包含多个操作时。

GraphQL 的核心优势之一是避免过度获取(over-fetching)。对于给定的对象类型,您只列出您需要的字段。

# 模式(Schema)片段
# type Student {
# id: ID!
# firstName: String
# lastName: String
# email: String
# collegeId: String
# }
# type Query {
# students: [Student]
# }
# 请求所有学生只包含 ID 和 firstName 的查询
query GetStudentNames {
students {
id
firstName
# lastName 和 email 未被请求
}
}

响应将只包含一个对象数组,每个对象都包含 id 和 firstName。

字段可以接受参数(Arguments)来过滤数据或标识特定对象。

# 模式(Schema)片段
# type Query {
# studentById(id: ID!): Student
# }
# 请求具有特定 ID 的学生的查询
query GetSpecificStudent {
studentById(id: "S1001") { # 传递参数 'id',值为 "S1001"
id
firstName
lastName
email
}
}

如果您需要在单个操作中多次查询同一个字段并使用不同的参数,则必须使用别名(Aliases)来避免响应 JSON 中的命名冲突。

# 使用别名请求两个不同学生的查询
query GetMultipleStudents {
student1001: studentById(id: "S1001") { # 别名 'student1001'
id
firstName
}
student1002: studentById(id: "S1002") { # 别名 'student1002'
id
firstName
}
}
# 响应结构
# {
# "data": {
# "student1001": { ... },
# "student1002": { ... }
# }
# }

片段(Fragments)是可重用的字段单元。当您需要在同一类型的多个对象上使用同一组字段时,它们有助于分解复杂查询或避免重复。

# 在 Student 类型上定义一个片段
fragment StudentCoreDetails on Student {
id
firstName
lastName
}
# 在查询中使用片段
query GetStudentsWithCoreDetails {
student1001: studentById(id: "S1001") {
...StudentCoreDetails # 在此处展开片段字段
email # 请求查询此部分特有的附加字段
}
allStudents: students {
...StudentCoreDetails # 在此处重用片段
}
}

直接将参数硬编码到查询字符串中不够灵活。变量(Variables)允许您在查询之外传递动态值。这使得查询可重用,并且对于安全性至关重要(防止注入问题)。

使用变量涉及三个步骤:

  1. 在查询定义中使用 $ 前缀声明变量,并指定其类型(例如,$studentId: ID!)。
  2. 在查询字段中使用变量(例如,studentById(id: $studentId))。
  3. 在查询字符串之外发送一个单独的 JSON 字典,将变量名映射到其值。
# 1 & 2:使用变量定义的查询
query GetStudentWithVariable($studentId: ID!) {
studentById(id: $studentId) {
id
firstName
lastName
}
}
# 3:随请求发送的单独的变量 JSON
{
"studentId": "S1002"
}

GraphQL 客户端(如 Apollo Client,或 Apollo Sandbox/GraphiQL 等工具)提供了发送查询字符串和变量对象两种数据的方式。

指令(Directives)提供了一种根据变量条件性地包含或跳过字段或片段的方式。常见的内置指令有 @include(if: Boolean!) 和 @skip(if: Boolean!)。

# 使用指令的条件字段查询
query GetStudentWithOptionalEmail($studentId: ID!, $includeEmail: Boolean!) {
studentById(id: $studentId) {
id
firstName
email @include(if: $includeEmail) # 仅当 $includeEmail 为 true 时包含 email
collegeId @skip(if: $includeEmail) # 如果 $includeEmail 为 true 则跳过 collegeId
}
}
# 变量 JSON 示例 1 (includeEmail = true)
{
"studentId": "S1001",
"includeEmail": true
}
# -> 响应将包含 'email',跳过 'collegeId'
# 变量 JSON 示例 2 (includeEmail = false)
{
"studentId": "S1001",
"includeEmail": false
}
# -> 响应将跳过 'email',包含 'collegeId'

示例 1:使用自定义字段解析器的查询

Section titled “示例 1:使用自定义字段解析器的查询”

有时,您想要查询的字段并不直接存在于您的数据库模型中,但可以从其他字段计算得出。您可以使用该字段的特定解析器(Resolver)来处理这种情况。

目标:为学生查询一个 fullName 字段,该字段由 firstName 和 lastName 派生而来。

将 fullName 字段添加到您的模式(Schema)中的 Student 类型:

# 在 schema.graphql 或 server.js 中
type Student {
id: ID!
firstName: String
lastName: String
fullName: String # 计算得出的字段
# ... 其他字段
}

步骤 2:为自定义字段添加解析器

Section titled “步骤 2:为自定义字段添加解析器”

在您的 resolvers.js(或等效文件)中,在 Student 类型的解析器映射内部专门为 fullName 字段添加一个解析器:

// 在 resolvers.js 中
const resolvers = {
Query: {
// ... 您的 Query 解析器(例如 students, studentById)
students: () => db.students.list(),
studentById: (_, { id }) => db.students.get(id),
},
Student: { // Student 类型内部字段的解析器
fullName: (parent, args, context, info) => {
// 这里的 'parent' 是由 Query 解析器返回的学生对象
// 从父对象访问 firstName 和 lastName
console.log(`Resolving Student.fullName for student ID: ${parent.id}`);
return `${parent.firstName} ${parent.lastName}`;
},
// 如果 id, firstName, lastName 直接可用,则无需为其定义解析器
// 在 Query 解析器返回的父对象上。
},
};

当您查询 fullName 时,GraphQL 会首先使用 Query.students 或 Query.studentById 解析器解析父级 Student 对象,然后执行 Student.fullName 解析器,将解析后的学生对象作为 parent 参数传递。

运行您的服务器并使用 Apollo Sandbox 执行一个请求 fullName 的查询:

query GetStudentFullNames {
students {
id
fullName # 请求计算字段
}
}

响应应包含组合后的全名。

GraphQL 在一次请求中获取相关数据方面表现出色。

目标:获取学生详细信息以及他们关联的学院信息。

确保您的模式(Schema)定义了 College 类型并从 Student 类型链接了它:

# 在 schema.graphql 或 server.js 中
type College {
id: ID!
name: String
location: String
}
type Student {
id: ID!
firstName: String
lastName: String
collegeId: String # 外键(数据可能这样存储)
college: College # 表示关系的字段
}
type Query {
students: [Student]
# ... 其他查询
}

为 Student.college 字段添加一个解析器。这个解析器将使用父学生对象中的信息(如 collegeId)来获取相关的学院数据。

// 在 resolvers.js 中
// 假设您的模拟 db.js 中存在 db.colleges.get(id)
const resolvers = {
Query: {
// ... Query 解析器
students: () => db.students.list(),
},
Student: {
// ... 其他 Student 解析器,如 fullName
// 'college' 关系字段的解析器
college: (parent, args, context, info) => {
// 'parent' 是学生对象
console.log(`Resolving Student.college for student ID: ${parent.id}, collegeId: ${parent.collegeId}`);
// 使用父学生对象的 collegeId 来获取学院
return db.colleges.get(parent.collegeId);
},
},
// 如果需要,可能需要为 College 字段添加解析器
};

这个解析器会为 Query.students 返回的每个学生运行。它从学生对象(parent.collegeId)中获取 collegeId,并使用它在(模拟的)学院数据源中查找相应的学院。

运行您的服务器并在 Apollo Sandbox 中执行嵌套查询:

query GetStudentsAndColleges {
students {
id
firstName
college { # 查询关系字段
# 请求嵌套 College 对象中的字段
id
name
location
}
}
}

响应将包含一个学生数组,每个学生都有一个嵌套的 college 对象,其中包含请求的学院详细信息,所有这些都在一次对服务器的请求中完成。