GraphQL 查询语言
GraphQL查询语言是客户端与服务器交互的核心。通过查询语言,客户端可以精确地描述需要的数据。本章将详细介绍查询语法、变量、片段、指令等核心概念。
基本查询
字段选择
GraphQL查询的核心是选择字段。查询的形状与返回数据的形状一致。
基本查询:
query {
user(id: "123") {
name
email
}
}
响应:
{
"data": {
"user": {
"name": "张三",
"email": "[email protected]"
}
}
}
查询的特点:
- 查询结构与响应结构一致
- 只返回请求的字段
- 字段可以是标量类型或对象类型
嵌套查询
当字段返回对象类型时,可以继续选择嵌套字段:
query {
user(id: "123") {
name
email
articles {
id
title
createdAt
tags
}
}
}
响应:
{
"data": {
"user": {
"name": "张三",
"email": "[email protected]",
"articles": [
{
"id": "1",
"title": "GraphQL入门",
"createdAt": "2024-10-20T10:00:00Z",
"tags": ["GraphQL", "API"]
},
{
"id": "2",
"title": "REST API设计",
"createdAt": "2024-10-21T10:00:00Z",
"tags": ["REST", "API"]
}
]
}
}
}
列表查询
查询返回列表的字段:
query {
users(limit: 5) {
id
name
email
}
}
响应:
{
"data": {
"users": [
{ "id": "1", "name": "张三", "email": "[email protected]" },
{ "id": "2", "name": "李四", "email": "[email protected]" },
{ "id": "3", "name": "王五", "email": "[email protected]" }
]
}
}
参数传递
字段可以接受参数:
query {
user(id: "123") {
name
articles(limit: 3, status: PUBLISHED) {
title
}
}
}
参数可以是标量类型、枚举类型或输入类型。
多字段查询
一次查询可以请求多个字段:
query {
user(id: "123") {
name
email
}
articles(limit: 5) {
id
title
}
me {
id
name
}
}
Query类型的多个字段会并行执行,提高查询效率。
操作名称
操作类型
GraphQL支持三种操作类型:
query GetUser {
user(id: "123") {
name
}
}
mutation CreateUser {
createUser(input: { name: "张三", email: "[email protected]" }) {
id
name
}
}
subscription OnCommentAdded {
onCommentAdded(articleId: "1") {
id
content
}
}
操作类型关键字:
query:查询操作,用于读取数据mutation:变更操作,用于修改数据subscription:订阅操作,用于实时数据
操作名称
为操作命名可以提高代码可读性,便于调试:
query GetUserProfile {
user(id: "123") {
name
email
articles {
title
}
}
}
操作名称的作用:
- 在日志中标识操作
- 在开发工具中显示
- 在Apollo Client等客户端中用于缓存key
简写语法
对于Query操作,可以省略query关键字:
{
user(id: "123") {
name
email
}
}
但推荐始终使用完整语法,便于添加操作名称和变量。
变量(Variables)
为什么使用变量
将动态值从查询中分离出来,有以下好处:
- 查询字符串可以静态定义
- 避免字符串拼接的安全风险
- 便于缓存查询
- 支持类型检查
变量定义
变量在操作名称后定义:
query GetUser($id: ID!, $withArticles: Boolean!) {
user(id: $id) {
name
email
articles @include(if: $withArticles) {
title
}
}
}
变量定义语法:$变量名: 类型
变量类型可以是:
- 标量类型:
Int、String、Boolean、ID、Float - 枚举类型:
ArticleStatus - 输入类型:
ArticleFilterInput - 列表类型:
[String!] - 非空类型:
String!
变量传递
变量在单独的JSON对象中传递:
{
"id": "123",
"withArticles": true
}
完整的请求:
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query": "query GetUser($id: ID!, $withArticles: Boolean!) { user(id: $id) { name email articles @include(if: $withArticles) { title } } }",
"variables": {
"id": "123",
"withArticles": true
},
"operationName": "GetUser"
}
默认变量值
可以为变量定义默认值:
query GetArticles($limit: Int = 10, $status: ArticleStatus = PUBLISHED) {
articles(limit: $limit, status: $status) {
id
title
}
}
如果调用时不传递变量,将使用默认值。
输入类型变量
复杂参数可以使用输入类型:
query SearchArticles($filter: ArticleFilterInput!) {
articles(filter: $filter) {
id
title
status
}
}
变量值:
{
"filter": {
"status": "PUBLISHED",
"authorId": "123",
"tags": ["GraphQL", "API"],
"createdAfter": "2024-01-01T00:00:00Z"
}
}
片段(Fragments)
什么是片段
片段是可复用的字段选择集,用于在多个查询中复用相同的字段选择。
定义片段
fragment UserFields on User {
id
name
email
avatar
createdAt
}
fragment ArticleFields on Article {
id
title
summary
createdAt
author {
...UserFields
}
}
片段语法:fragment 片段名 on 类型 { 字段... }
使用片段
使用...片段名语法引用片段:
query GetUserWithArticles($userId: ID!) {
user(id: $userId) {
...UserFields
articles {
...ArticleFields
}
}
}
fragment UserFields on User {
id
name
email
}
fragment ArticleFields on Article {
id
title
author {
...UserFields
}
}
片段的优势
代码复用:避免重复定义相同的字段选择。
query GetUsers {
users {
...UserFields
}
}
query GetUser {
user(id: "123") {
...UserFields
}
}
query GetArticle {
article(id: "1") {
title
author {
...UserFields
}
}
}
fragment UserFields on User {
id
name
email
avatar
}
组件化:前端组件可以定义自己的片段,组合成完整查询。
query GetArticlePage($id: ID!) {
article(id: $id) {
...ArticleHeader
...ArticleContent
...ArticleComments
...ArticleSidebar
}
}
fragment ArticleHeader on Article {
title
author {
name
avatar
}
createdAt
}
fragment ArticleContent on Article {
content
tags
}
fragment ArticleComments on Article {
comments {
id
content
author {
name
}
}
}
fragment ArticleSidebar on Article {
relatedArticles {
title
summary
}
}
内联片段
内联片段用于处理接口和联合类型:
query Search($keyword: String!) {
search(keyword: $keyword) {
... on User {
id
name
email
}
... on Article {
id
title
summary
}
... on Comment {
id
content
}
}
}
内联片段也可以用于条件选择:
query GetUser($id: ID!) {
user(id: $id) {
id
name
... on User {
email
}
}
}
片段中使用变量
片段可以访问操作中定义的变量:
query GetUser($id: ID!, $withEmail: Boolean!) {
user(id: $id) {
...UserFields
}
}
fragment UserFields on User {
id
name
email @include(if: $withEmail)
}
指令(Directives)
什么是指令
指令用于动态改变查询的执行方式。GraphQL规范定义了两个核心指令:@include和@skip。
@include指令
@include(if: Boolean):条件为true时包含该字段。
query GetUser($id: ID!, $withArticles: Boolean!) {
user(id: $id) {
name
email
articles @include(if: $withArticles) {
id
title
}
}
}
变量:
{
"id": "123",
"withArticles": true
}
@skip指令
@skip(if: Boolean):条件为true时跳过该字段。
query GetArticles($skipContent: Boolean!) {
articles {
id
title
content @skip(if: $skipContent)
}
}
变量:
{
"skipContent": true
}
@deprecated指令
@deprecated(reason: String):标记字段已废弃。
type User {
id: ID!
name: String!
fullName: String @deprecated(reason: "使用 name 字段代替")
}
自定义指令
服务器可以定义自定义指令:
directive @auth(requires: Role!) on FIELD_DEFINITION
directive @cache(ttl: Int!) on QUERY
directive @validate(pattern: String!) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
使用自定义指令:
query @cache(ttl: 60) {
articles {
id
title
}
}
别名(Aliases)
为什么需要别名
当需要查询同一字段多次但参数不同时,或者想要重命名字段时,需要使用别名。
基本用法
query {
me: user(id: "123") {
name
}
friend: user(id: "456") {
name
}
}
响应:
{
"data": {
"me": {
"name": "张三"
},
"friend": {
"name": "李四"
}
}
}
实际应用场景
获取不同状态的文章:
query {
publishedArticles: articles(status: PUBLISHED) {
id
title
}
draftArticles: articles(status: DRAFT) {
id
title
}
archivedArticles: articles(status: ARCHIVED) {
id
title
}
}
多语言内容:
query {
article(id: "1") {
titleEN: title(language: EN)
titleCN: title(language: CN)
titleJP: title(language: JP)
}
}
Mutation操作
基本Mutation
mutation CreateUser {
createUser(input: {
name: "张三"
email: "[email protected]"
}) {
id
name
email
createdAt
}
}
使用变量的Mutation
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
变量:
{
"input": {
"name": "张三",
"email": "[email protected]"
}
}
返回修改后的数据
Mutation后返回更新后的数据:
mutation UpdateArticle($id: ID!, $input: UpdateArticleInput!) {
updateArticle(id: $id, input: $input) {
id
title
content
updatedAt
}
}
多个Mutation
一个操作中可以有多个Mutation,它们会按顺序执行:
mutation CreateMultipleUsers {
user1: createUser(input: { name: "张三", email: "[email protected]" }) {
id
name
}
user2: createUser(input: { name: "李四", email: "[email protected]" }) {
id
name
}
}
Subscription操作
基本Subscription
subscription OnCommentAdded($articleId: ID!) {
onCommentAdded(articleId: $articleId) {
id
content
author {
name
}
createdAt
}
}
Subscription特点
- 基于WebSocket实现
- 服务器主动推送数据
- 适合实时场景(聊天、通知、协作)
常见使用场景
subscription {
onArticleCreated {
id
title
author {
name
}
}
}
subscription {
onUserStatusChanged(userId: "123") {
id
isOnline
lastSeenAt
}
}
subscription {
onNotificationReceived {
id
type
message
createdAt
}
}
查询最佳实践
命名规范
始终为操作命名:
query GetUserProfile($id: ID!) {
user(id: $id) {
name
email
}
}
使用变量
避免硬编码参数值:
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
合理使用片段
对于重复的字段选择,使用片段:
query GetUsers {
users {
...UserFields
}
}
fragment UserFields on User {
id
name
email
}
控制查询深度
避免过深的嵌套查询:
query {
users {
articles {
comments {
author {
articles {
comments {
content
}
}
}
}
}
}
}
服务器应该限制查询深度,防止性能问题。
使用分页
对于列表数据,始终使用分页:
query {
articles(first: 10, after: "cursor") {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
总结
GraphQL查询语言的核心概念:
- 字段选择:精确选择需要的字段
- 操作类型:Query、Mutation、Subscription
- 变量:将动态值从查询中分离
- 片段:复用字段选择集
- 指令:动态改变查询行为
- 别名:重命名字段或查询同一字段多次
掌握这些概念,可以编写出高效、可维护的GraphQL查询。