跳到主要内容

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
}
}
}

变量定义语法:$变量名: 类型

变量类型可以是:

  • 标量类型:IntStringBooleanIDFloat
  • 枚举类型: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查询语言的核心概念:

  1. 字段选择:精确选择需要的字段
  2. 操作类型:Query、Mutation、Subscription
  3. 变量:将动态值从查询中分离
  4. 片段:复用字段选择集
  5. 指令:动态改变查询行为
  6. 别名:重命名字段或查询同一字段多次

掌握这些概念,可以编写出高效、可维护的GraphQL查询。