不同的分页模型支持不同的客户端功能
GraphQL 中一个常见的用例是在对象集中遍历关系。在 GraphQL 中,这些关系可以以多种不同的方式暴露,为客户端开发人员提供不同的功能集。
暴露对象之间连接的最简单方法是使用返回复数类型的字段。例如,如果我们想获取 R2-D2 的朋友列表,我们可以直接请求所有朋友
但是,我们很快就会意识到,客户端可能需要额外的行为。客户端可能希望能够指定他们想要获取的朋友数量;也许他们只想要前两个。所以我们想暴露类似的东西
{ hero { name friends(first: 2) { name } }}
但是,如果我们只获取了前两个,我们可能还想对列表进行分页;一旦客户端获取了前两个朋友,他们可能希望发送第二个请求来获取接下来的两个朋友。我们如何实现这种行为呢?
我们可以通过多种方式进行分页
friends(first:2 offset:2)
的方法来请求列表中的接下来的两个朋友。friends(first:2 after:$friendId)
的方法来请求我们在最后一次获取后接下来的两个朋友。friends(first:2 after:$friendCursor)
的方法,其中我们从最后一个项目获取一个游标,并使用它进行分页。总的来说,我们发现 **基于游标的分页** 是那些设计中最强大的。特别是如果游标是不透明的,无论是基于偏移量还是基于 ID 的分页都可以使用基于游标的分页来实现(通过使游标成为偏移量或 ID),并且使用游标在将来分页模型发生变化时提供了额外的灵活性。为了提醒游标是不透明的,并且不应依赖于它们的格式,我们建议对它们进行 base64 编码。
这导致了一个问题;但是;我们如何从对象中获取游标?我们不希望游标存在于 User
类型上;它是连接的属性,而不是对象的属性。所以我们可能想引入一个新的间接层;我们的 friends
字段应该给我们一个边列表,而边同时具有游标和底层节点
{ hero { name friends(first: 2) { edges { node { name } cursor } } }}
如果存在特定于边的信息,而不是特定于其中一个对象的信息,那么边的概念也很有用。例如,如果我们想在 API 中暴露“友谊时间”,那么将其放在边上是一个自然的地方。
现在我们能够使用游标对连接进行分页,但我们如何知道何时到达连接的末尾呢?我们必须不断查询,直到返回一个空列表,但我们希望连接能够告诉我们何时到达末尾,这样我们就无需进行额外的请求。类似地,如果我们想了解有关连接本身的更多信息,例如,R2-D2 有多少个朋友?
为了解决这两个问题,我们的 friends
字段可以返回一个连接对象。连接对象将包含一个用于边的字段,以及其他信息(如总计和有关是否存在下一页的信息)。因此,我们的最终查询可能看起来更像
{ hero { name friends(first: 2) { totalCount edges { node { name } cursor } pageInfo { endCursor hasNextPage } } }}
请注意,我们还可能在 PageInfo
对象中包含 endCursor
和 startCursor
。这样,如果我们不需要边包含的任何其他信息,我们就不需要查询边,因为我们从 pageInfo
中获得了分页所需的游标。这为连接带来了潜在的可用性改进;除了公开 edges
列表之外,我们还可以公开一个专门的节点列表,以避免间接层。
显然,这比我们最初仅使用复数的设计更复杂!但通过采用这种设计,我们为客户端解锁了许多功能
totalCount
或 pageInfo
。cursor
或 friendshipTime
。要实际体验一下,示例模式中有一个额外的字段,名为friendsConnection
,它展示了所有这些概念。您可以在示例查询中查看它。尝试删除friendsConnection
的after
参数,看看分页将如何受到影响。另外,尝试用连接上的辅助friends
字段替换edges
字段,这样您就可以直接获取朋友列表,而无需额外的间接边缘层,当这对客户端合适时。
为了确保这种模式的一致实现,Relay 项目有一个正式的规范,您可以遵循它来构建使用基于游标的连接模式的 GraphQL API。