理解GraphQL中的查询

来自菜鸟教程
跳转至:导航、​搜索

介绍

在本教程中,我们将深入了解 GraphQL 中的查询,以便您更好地了解如何从 GraphQL 服务器检索数据。 我们将介绍字段、参数、别名、操作语法等。 完成后,您将能够利用查询的功能来使用 GraphQL 构建更好的 API。

字段

GraphQL 基于请求对象上的特定 字段 。 因此,我们不能不谈字段就成功谈查询。 查询是客户端用来从服务器请求特定字段的构造。

鉴于 GraphQL 的结构是为所有请求以最佳方式公开一个端点,查询的结构是请求特定的字段,服务器的结构同样是响应所请求的确切字段。

考虑客户端想要从 API 端点请求足球运动员的情况。 查询的结构如下:

{
    players {
        name
    }
}

这是一个典型的 GraphQL 查询。 查询由两个不同的部分组成:

  • root field(玩家):包含有效载荷的对象。
  • payload(名称):客户端请求的字段。

这是 GraphQL 的重要组成部分,因为服务器知道客户端要求哪些字段,并始终以准确的数据进行响应。 在我们的示例查询中,我们可以得到这样的响应:

{
    "players": [
        {"name": "Pogba"},
        {"name": "Lukaku"},
        {"name": "Rashford"},
        {"name": "Marshal"}
    ]
}

name 字段返回一个字符串类型,在本例中为曼联球员的姓名。 但是,我们不仅限于字符串:我们可以拥有所有数据类型的字段,就像根字段 players 返回一个项目数组。 随意在 GraphQL 官方文档中了解有关 GraphQL 类型系统 的更多信息。

在生产中,我们想做的不仅仅是返回名称。 例如,在我们的最后一个查询中,我们可以重新定义查询以从列表中选择单个玩家并查询有关该玩家的更多数据。 为此,我们需要一种方法来识别该玩家,以便我们可以获取详细信息。 在 GraphQL 中,我们可以使用 arguments 来实现这一点。

论据

GraphQL 查询允许我们将参数传递给查询字段和嵌套查询对象。 您可以将参数传递给查询中的每个字段和每个嵌套对象,以进一步加深您的请求并进行多次提取。 参数的用途与 REST API 中的传统 query parametersURL segments 相同。 我们可以将它们传递到我们的查询字段中,以进一步指定服务器应如何响应我们的请求。

回到我们之前获取特定球员套件详细信息的情况,例如 shirt sizeshoe size。 首先,我们必须通过传入参数 id 来指定该播放器,以从播放器列表中识别播放器,然后在查询有效负载中定义我们想要的字段:

{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

在这里,由于我们传递给查询的 id 参数,我们在播放器 Pogba 上请求所需的字段。 就像字段一样,没有类型限制。 参数也可以是不同的类型。 使用 id 参数的上一个查询的结果将如下所示:

{
    "player": {
        "name": "Pogba",
        "kit": [
            {
                "shirtSize": "large",
                "shoeSize": "medium"
            }
        ]
    }
}

这里一个可能的陷阱是 GraphQL 查询对于单个项目和项目列表看起来几乎相同。 在这些情况下,请记住,我们总是根据模式中定义的内容知道会发生什么。

此外,GraphQL 查询是交互式的,因此您可以随意向根字段对象添加更多字段。 这样,作为客户的您可以灵活地避免往返,并在单个请求中请求尽可能多的数据。

现在,如果我们想为两个玩家获取相同的字段,而不仅仅是一个,会发生什么? 这就是 别名 的用武之地。

别名

如果您仔细查看我们的最后一个示例,您会注意到结果对象字段:

// result....
"player": {
    "name": "Pogba",
    "kit": [
        {
            "shirtSize": "large",
            "shoeSize": "medium"
        }
    ]
}

匹配查询字段:

// query.... has matching fields with the result
player(id : "Pogba") {
    name,
    kit {
        shirtSize,
        bootSize
    }
}

但没有论据:

(id : "Pogba")

因此,我们不能直接用不同的参数查询相同的字段 player。 也就是说,我们不能这样做:

{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
    player(id : "Lukaku") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

我们不能这样做,但我们可以做的是使用别名。 他们让我们将字段的结果重命名为我们想要的任何内容。 对于我们的示例,要查询两个球员的球衣详细信息,我们将这样定义我们的查询:

{
    player1: player(id: "Pogba") {
        name,
        kit {
            shirtSize,
            shoeSize
        }
    }
    player2: player(id: "Lukaku") {
        name,
        kit {
            shirtSize,
            shoeSize
        }
    }
}

在这里,两个 player 字段会发生冲突,但由于我们可以将它们别名为不同的名称 player1player2,我们可以在一个请求中获得两个结果:

{
    "data": {
        "player1": {
            "name": "Pogba",
            "kit": [
                {
                    "shirtSize": "large",
                    "shoeSize": "medium"
                }
            ]
        },
        "player2": {
            "name": "Lukaku",
            "kit": [
                {
                    "shirtSize": "extralarge",
                    "shoeSize": "large"
                }
            ]
        }
    }
}

现在使用别名,我们已经成功地用不同的参数查询了同一个字段并得到了预期的响应。

操作语法

到目前为止,我们一直在使用速记操作语法,我们没有明确要求定义操作名称或类型。 在生产环境中,最佳实践是使用操作名称和类型来帮助减少代码库的歧义。 它还有助于在出现错误时调试您的查询。

操作语法包括两个核心内容:

  • operation type 可以是查询、变异或订阅。 它用于描述您打算执行的操作类型。
  • operation name 可以是任何可以帮助您与您尝试执行的操作相关的东西。

现在,我们可以重写前面的示例并添加一个操作类型和名称,如下所示:

query PlayerDetails{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

在本例中,query 是操作类型,PlayerDetails 是操作名称。

变量

到目前为止,我们一直将所有参数直接传递到查询字符串中。 在大多数情况下,我们传递的参数是动态的。 例如,假设客户想要详细信息的播放器来自文本输入表单或下拉菜单。 我们传递给查询字符串的参数必须是动态的,为此,我们需要使用 variables。 因此,变量用于从查询中提取动态值并将它们作为单独的字典传递。

考虑到我们的最后一个示例,如果我们想让玩家动态以返回所选玩家的详细信息,我们必须将玩家的 ID 值存储在一个变量中并将其传递给操作名称和查询参数,如下所示:

query PlayerDetails ($id: String){
    player (id : $id) {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

这里,$title: String是变量定义,title是变量名。 它以 $ 为前缀,后跟类型,在本例中为 String。 这意味着我们可以避免手动插入字符串来构造动态查询。

碎片

查看我们的查询,您会注意到两个玩家的 player 字段实际上是相同的:

        name,
        kit {
            shirtSize,
            bootSize
        }

为了提高查询效率,我们可以将这段共享逻辑提取到 player 字段上的可重用片段中,如下所示:

{
    player1: player(id: "Pogba") {
        ...playerKit
    }
    player2: player(id: "Lukaku") {
        ...playerKit
    }
}

fragment playerKit on player {
    name,
    kit {
        shirtSize,
        shoeSize
    }
}

提取一段共享代码并在多个领域重用它的能力是一个关键概念,它可以帮助开发人员避免在开发甚至生产中重复自己。 如果您正在处理一个深度分层的代码库,您会发现重用代码而不是在多个组件中重复自己是非常有用和及时的。

指令

GraphQL 指令为我们提供了一种方法来告诉服务器在响应我们的查询时是 include 还是 skip 特定字段。 GraphQL 中有两个内置指令可以帮助我们实现这一目标:

  • @skip:当传入的值为真时跳过特定字段。
  • @include:当传入的值为真时包含特定字段。

让我们添加一个 Boolean 指令并在服务器上使用 @skip 指令跳过它:

query PlayerDetails ($playerShirtDirective: Boolean!){
    player(id: "Pogba") {
        name,
        kit {
            shirtSize @skip(if: $playerShirtDirective)
            bootSize
        }
    }
}

接下来我们要做的是在查询变量中创建 playerShirtDirective 指令并将其设置为 true:

// Query Variables
{
  "itemIdDirective": true
}

现在这将返回没有 shirtSize 的有效负载:

"player": {
    "name": "Pogba",
    "kit": [
        {
            "shoeSize": "medium"
        }
    ]
}

我们可以使用 @include 指令来扭转这种情况。 它与 @skip 指令相反。 您可以通过在查询中将 @skip 指令替换为 @include 来使用它来反转服务器上的此跳过操作。

结论

在本文中,我们详细介绍了 GraphQL 查询。

如果您想了解更多关于 GraphQL 的信息,请查看 GraphQL 官方文档。 如果您想尝试使用 GraphQL 制作项目,请尝试我们的 如何在 Ubuntu 18.04 上使用 Node.js 和 MongoDB 构建和部署 GraphQL 服务器教程。