一、核心概念与思想
这是理解 GraphQL 的基石,关键在于思维模式的转变。
1.1 核心思想:从"自助餐厅"到"点餐"
- REST API (自助餐厅): 服务器提供一系列固定的套餐 (Endpoints),如
/users/1
、/users/1/posts
。客户端只能选择这些套餐,常常会拿到不需要的数据(过度获取),或者需要多次选择才能凑齐想要的数据(多次请求)。 - GraphQL (点餐): 服务器只提供一张详尽的菜单 (Schema)。客户端按需填写一张点餐单(一次请求),精确地描述所有需要的数据。服务器一次性地、不多不少地将所有内容返回。
1.2 Schema: API 的唯一"菜单" (The Single Source of Truth)
Schema 是前后端之间最重要的"合同",它使用 Schema Definition Language (SDL) 定义了 API 的所有能力。
1.3 Schema 的构成要素
-
类型 (Types):
-
标量类型 (Scalar Types): GraphQL 内置的基础类型:
String
,Int
,Float
,Boolean
,ID
。 -
对象类型 (Object Types): 根据业务需求自定义的复杂类型,如
User
,Post
。 -
类型修饰符
!
和[]
: 这是定义类型时至关重要的部分。!
(感叹号): 代表**"非空 (Non-Null)"**。加了!
的字段,服务器承诺返回结果时该字段的值绝不会是null
。[]
(方括号): 代表**"列表 (List)"**,即一个数组。
-
组合使用
!
和[]
:[User]
: 可空列表,元素也可空 (很少用)。[User!]
: 可空列表,元素非空 (常用)。[User]!
: 非空列表,元素可空。[User!]!
: 非空列表,元素非空 (最佳实践)。
-
-
三大根类型 (Root Types): API 的操作入口。
Query
: "读"操作入口。Mutation
: "写"操作入口。Subscription
: 实时数据入口。
-
如何表示"没有数据":
- 查询单个对象: 如果对象不存在 (如
user(id: "not-found")
),Schema 中应定义其返回类型为可空 (如user: User
),服务器应返回null
。 - 查询列表: 如果查询结果为空 (如
users(filter: {name: "nobody"})
),Schema 中应定义其返回类型为列表 (如users: [User!]!
),服务器应返回一个空列表[]
。
- 查询单个对象: 如果对象不存在 (如
1.4 查询语言 (Query Language)
客户端用来"点餐"的语言,其核心是"所见即所得"。
-
查询结构:
- 字段 (Fields): 按需选择。
- 参数 (Arguments):
field(key: value)
。 - 嵌套 (Nesting):
{}
获取关联数据。
1.5 操作的两种形式:匿名操作 vs. 具名操作
在编写查询或变更时,有两种形式,理解它们的区别对于使用变量至关重要。
-
匿名操作 (Anonymous Operation):
这是一种简写形式,直接以 { 开头。它简单快捷,非常适合在工具中进行快速、一次性的测试。
GraphQL# 这是一个匿名查询 { character(id: "1") { name } }
限制: 匿名操作不支持定义或使用变量。
-
具名操作 (Named Operation):
这是完整的、更规范的写法,由三部分构成:操作类型 操作名称(变量定义) { ... }。
-
操作类型:
query
,mutation
, 或subscription
。 -
操作名称: 你为这个操作取的名字,如
GetCharacterById
,便于调试和日志记录。 -
变量定义: 在
()
中定义该操作需要的所有变量。
GraphQL# 这是一个具名查询,但没有变量 query GetCharacterName { character(id: "1") { name } } # 这是一个带变量的具名查询 query GetCharacterById($charId: ID!) { character(id: $charId) { name } }
核心规则:只要你想在操作中使用动态变量,就必须使用"具名操作"的完整形式。
-
1.6 基础语法规则
- 无通配符
*
: 必须明确列出所有需要的字段。 - 引号规则: 字符串必须 使用双引号 (
"
) 。 - 输入 vs 输出: 输入参数用
key: value
,输出字段只需key
。
二、在线实战练习
使用 Rick and Morty GraphQL API 作为在线练习平台。
1. 基础查询
获取第一个角色的 name
, species
, gender
。
GraphQL
query { character(id: 1) { name, species, gender } }
2. 嵌套查询
获取 Morty (id: 2
) 的 name
及其 origin
和 location
的 name
。
GraphQL
query { character(id: 2) { name, origin { name }, location { name } } }
3. 列表与参数
查找所有名为 "Jerry Smith" 的角色的 id
和 name
。
GraphQL
query { characters(filter: { name: "Jerry Smith" }) { results { id, name } } }
4. 别名 (Alias)
一次请求同时获取 id
为 1 和 2 的角色,并重命名为 rick
和 morty
。
GraphQL
query { rick: character(id: 1) { name }, morty: character(id: 2) { name } }
5. 片段 (Fragment)
使用片段优化上一个查询,避免重复书写字段。
GraphQL
query {
rick: character(id: 1) { ...CharacterInfo }
morty: character(id: 2) { ...CharacterInfo }
}
fragment CharacterInfo on Character { name, status, species, gender }
6. 变量 (Variables)
编写一个通用查询,通过变量动态传入 id
。
Query:
GraphQL
query GetCharacterById($charId: ID!) { character(id: $charId) { name, image } }
Variables (JSON):
JSON
{ "charId": "183" }
三、进阶技巧与核心规则释疑
本节将详细阐述我们之前讨论过的所有进阶问题。
3.1 深入理解参数与变量的多种情况
-
场景一:为一个参数提供多个"条件" (多条件过滤)
-
规则: 将多个条件作为键值对,放在同一个
filter
对象中。它们之间的逻辑关系为 AND。 -
示例: 查询所有物种为 "Human" 且状态为 "Alive" 的角色。
GraphQLquery { characters(filter: { species: "Human", status: "Alive" }) { results { name species status } } }
-
-
场景二:一次性查询多个 ID (输入数组参数)
-
规则: 直接在参数中以内联方式或通过变量传入一个数组
[]
。 -
示例: 使用
charactersByIds
查询id
为 1 和 2 的角色。-
硬编码方式:
GraphQLquery { charactersByIds(ids: ["1", "2"]) { id name } }
-
变量方式 (Query):
GraphQLquery GetCharsByIds($idList: [ID!]!) { charactersByIds(ids: $idList) { id name } }
-
变量方式 (Variables JSON):
JSON{ "idList": ["1", "2"] }
-
-
-
场景三:为一个请求提供多个"动态变量"
-
规则: 在
query
定义的()
中用逗号,
分隔多个变量声明;在Variables
JSON 中用逗号,
分隔多个键值对。 -
示例: 同时查询
id
为 2 的角色,以及所有状态为 "Alive" 的角色。-
Query:
GraphQLquery GetCharacterAndFilter($charId: ID!, $status: String!) { specificCharacter: character(id: $charId) { id name } filteredCharacters: characters(filter: { status: $status }) { results { id name } } }
-
Variables JSON:
JSON{ "charId": "2", "status": "Alive" }
-
-
3.2 链式查询 vs. 并行查询 ("一次请求到底能查多少东西?")
-
核心规则: 在一次请求的同一层级 ,你可以调用任意多个相互独立的"方法"(字段) 。但你不能将一个字段的输出结果作为同一请求内另一个字段的输入(链式依赖)。
-
并行查询 (Query):
query
中的多个顶级字段(如character
,location
)是相互独立的,服务器可以并行执行它们以提高效率。GraphQLquery { # 这三个字段会并行执行 character(id: "1") { name } location(id: "3") { name } episode(id: "5") { name } }
-
链式查询 (不支持): "先查A,再用A的结果查B"的逻辑,必须由客户端分两次请求完成。
3.3 mutation
的串行执行规则
-
规则:
mutation
中的多个顶级字段,即使在结构上是独立的,也必须由服务器严格按照其书写顺序,串行(一个接一个)执行。 -
目的: 这是为了保证数据修改的逻辑是可预测的,避免因并行执行而导致的数据错乱(竞态条件)。
GraphQLmutation { # 服务器保证先执行这个 op1: someCreateOperation(...) { id } # 再执行这个 op2: someUpdateOperation(...) { id } }
3.4 片段 (...
) 与指令 (@
)
-
...
(片段展开): 用于代码复用,特别适合与组件化的前端框架结合。 -
@
(指令): 用于在运行时改变查询行为。@include(if: Boolean)
: 当if
为true
时,包含该字段。@skip(if: Boolean)
: 当if
为true
时,跳过该字段。
四、工具使用:Postman
-
核心规则: 方法永远是
POST
,URL 永远是单一端点 ,所有内容都在Body
中。 -
操作步骤:
- 新建请求: 创建一个新请求,方法设为
POST
,填入 URL (例如:https://rickandmortyapi.com/graphql
)。 - 配置 Body: 点击
Body
标签页,选择GraphQL
选项。 - 填入内容: 在
Query
和GRAPHQL VARIABLES
输入框中分别粘贴查询语句和 JSON 变量。 - 发送请求: 点击
Send
。
- 新建请求: 创建一个新请求,方法设为
五、官方与推荐学习资源
-
GraphQL 官方网站:
- 官网首页: graphql.org/
- 官方入门教程 (必读): graphql.org/learn/
-
相关技术栈文档:
- .NET/C# (后端): Hot Chocolate 框架文档 chillicream.com/docs/hotcho...
- Vue (前端): Vue Apollo 文档 v4.apollo.vuejs.org/
- Postman: Postman 官方 GraphQL 指南 learning.postman.com/docs/sendin...