在GraphQL中使用游标分页实现分页

面试官:小伙子,你的数组去重方式惊艳到我了

分页是数据库和Web开发中广泛使用的技术。在GraphQL中,分页的实现可以有两种方式:基于页码的分页和基于游标的分页。通常情况下,使用基于游标的分页可以提供更佳的性能与用户体验。

基于页码的分页

基于页码的分页,顾名思义是基于页码进行分页操作。例如,在一次查询中,每页显示10个结果,我们可以为该查询定义一个page参数:

------------ ----- -
  ----------- ------ ------ --- -
    --
    ----
  -
-

上面这个查询将返回第$page页的10个用户数据,当page=1时,返回结果['User1', 'User2', ..., 'User10'];当page=2时,返回结果['User11', 'User12', ..., 'User20']

为了实现基于页码的分页,GraphQL服务器必须对结果进行限制,其结果会带有前后页的按钮链接,但是这种方式有几个缺点:

  • 首先,基于页码的分页不适合大型数据集,因为查询开销大。计算出所有可用的页码非常困难,因为它们取决于许多后端因素(例如集合大小和筛选器条件),并且也不适合最终用户的经验。
  • 其次,当集合被修改时,页码会改变。例如,当添加新的记录时,就会在后续页码中看到之前看到的内容,这损害了缓存兼容性和可预测性。

在GraphQL中,我们通常使用基于游标的分页来解决这些问题。

基于游标的分页

游标分页在GraphQL中的使用方式是调整现有查询需要的参数值即可,使用序列化的游标作为参数传递。一个基本的GraphQL请求包含以下参数:first表示每页取n条数据,after表示每页的游标值。例如:

----- -
  ------------ --- ------ ----- -
    ----- -
      ---- -
        --
        ----
      -
      ------
    -
    -------- -
      -----------
      -----------
      -----------
      ---------
    -
  -
-

这个查询将返回我们所有用户的列表,每一页展示10个。查询会在第一页中含有前10个用户数据,当再次请求第二页时,只需要将after参数设为当前页页尾节点的游标值即可。

这种方式有几个好处:

  • 第一,我们将返回所有的数据,用户将无法找到任何数据条目的漏洞。在后台,我们仅返回所请求的10项(或其他数量)数据,比起所有数据,这相对容易得多。
  • 其次,由于客户端和服务器不会混合缓存数据,因此缓存的可预测性更强。 接下来,我们将详细解释如何为GraphQL添加游标分页。

游标形式

通常,游标的形式为字符串或数字类型,它随着列表中的每个元素而变化,并且客户端通过请求下一个页面来现定游标的value。

我们可以使用全局唯一标识符(GUID)作为游标值:

------------ ----- -------

上面的示例代码是一个用于创建游标值的自定义指令,它通过GraphQL schema插入,然后在每个可能用作游标(Strings,Ints等) 中使用该指令。然后我们可以像这样将查询参数传递到schema:

----- -
  ------------ -- ------ --------------------------- -
    ----- -
      ---- -
        --
        ----
      -
      ------
    -
    -------- -
      -----------
      -----------
      -----------
      ---------
    -
  -
-

在上面的查询中,游标值是字符串"MTM6UHJvZmlsZURiOlZhbHVl"。我们使用上一个子集的最后一个元素ID作为游标值的生成方法,因此我们每次请求前一个子集时都可以使用它。

基于游标的分页的实现

我们可以看一下GraphQL的分页规范,理解基于游标的分页的实现。

--------- ---- -
  --- ---
-

---- -------- -
  ------------ --------
  ------------ --------
  ------------ ------
  ---------- ------
-

---- ---- -
  ------- -------
  ----- -----
-

---- ---------- -
  ------ ------
  --------- ---------
  ----------- ---
-

上面的类型定义包括三个类型:NodeEdgeConnectionNode是一个公共接口,它为每个拥有ID的对象定义一个字段。此外,我们定义了一个PageInfo类型,其中包含分页所需数据的有关信息。Edge类型表示Connection中的子集,connection作为对象列表的特定属性返回,并返回查询中使用的游标和查询中最后一次检索到的Node使用的游标。

Connection包含一组Edge对象以及一个关于这个集合的所有信息的PageInfo对象,其中包括该集合的总数以及可以在该集合中的键之间进行导航的光标。将Edge和PageInfo字面意义地串联到Connection类型中,并在返回的结果中使用connection属性即可实现链接类型,见下面的示例代码:

---- ----- -
  - -------------
  ------------ ---- ------ -------- ---------------
-

---- -------------- -
  --------- ---------
  ------ -----------
  ----------- ---
-

---- -------- -
  ----- -----
  ------- -------
-

---- ---- -
  --- ---
  ----- -------
  -------- -------
-

在我们定义的users查询中,我们会查询到一些用户,然后作为Connection对象返回。UserConnection对象有total_count属性,firstlast参数用于决定分页和游标。我们将使用users分配给UserConnectionedges来返回UserEdge对象的数组。而且,我们通过利用用户的唯一id,将节点的游标值与其唯一id绑定在一起,每次查询只需重复最后一个游标,即可定位到接下来的结果集。

客户端再fetch数据

在我们定义基于游标的分页之后,我们的客户端可以使用以下伪代码来获取真正的数据集,我们已假设在服务器上检索到第一子集并返回结果对象:

--- ------ - ----- --------------
  ------ ------------
  ---------- -
    ------ --
  -
--

-- -------- --- ---- ----
--- ------- - ----- --------------
  ------ ------------
  ---------- -
    ------ ---
    ------ ---------------------------------
  -
--

在上面的代码中,我们通过应用游标after来Fecth下一子集。它依赖于它在前一组中定位到最后一个元素的脚步result.data.users.edges[9].cursor。它定位到这一点只是因为我们传递了first=10请求,导致我们每个集包含了十个元素,而且也是因为我们在返回数据的时候传回了game edges节点的数组。

提交cursor到后台

客户端通常会在查询之间保留上一个关键游标,并根据需要将其提交到后端,以避免重复fetch以及浪费时间和资源。为此,我们可以将游标缓存于本地状态中,然后在用户尝试加载下一页时将其提交。

--- ----------- - ----
--- ------ - ------------------ - ---------

----- ------------- -
  ----- --- - ----- --------------
    ------ ------------
    ---------- -
      ------ ---
      ------ ------
    -
  --

  ----- -
    ------ ---------
    --------- - ------------ -------------- -
  - - --------------

  ----------- - --------------

  ------------------- -- --
    ------ ----------------------------
  ---

  ------ - ------------------------ - ---------
-

在上面的代码中,我们尝试不断地向后加载页面,直到所有数据都被加载。首先,我们定义了一个hasNextPage变量并将其设置为“true”,表示我们还有页面可以加载。然后我们将当前页的“endCursor”存储为一个变量cursor,以便我们可以将其提交到后端以拾取下一页。接下来,我们使用graphql客户端执行查询,并提供先前游标为'after: cursor'。由于我们定义了first变量为10,因此它只会返回下一页的前10个数据。每个查询都会返回一个PageInfo对象,其中包含hasNextPage值。只要我们的查询返回hasNextPage=true,我们就会持续地使用cursor和新edges向我们目前缓存的数据中添加新数据,直到我们返回的hasNextPage=false

另外,我们可以引入一个UI组件,例如Load More按钮,通过与游标相结合来加载数据。

游标的类型

基本上,游标可以是任何数据类型,它随列表中的每个元素而异并连续增长。我们可以使用不同的数据类型作为游标value,例如:

  • 日期或时间戳
  • 整数或双精度浮点数
  • 全球唯一标识符(GUID)
  • etc.

首要的是保证游标值的唯一性,并且它们的排序逻辑正确。一般情况下,使用时间戳类型的游标值较为流行,但使用字符串或共享游标的作用确实将它们广泛应用于各种类型。

结论

在使用基于游标的分页时,我们能够提供更好的性能和用户体验。当我们需要一个完整的,大型集合数据时,游标分页是正确的选择。而且,通过增加我们的GraphQL schema,我们可以轻松地添加基于游标的分页,指导我们在客户端中向后提取更多数据。

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/66fb5d8144713626015be300


猜你喜欢

  • 如何在 Mocha 测试中测试 Express 中间件

    在前端开发中,我们经常需要测试我们的代码以确保功能正确性和稳定性。在一个大型的 Express 应用中,中间件起到了非常重要的作用。为了保证中间件的正确性,我们需要对它们进行测试。

    21 天前
  • 如何避免 Java 程序死锁,提高程序性能?

    在 Java 程序开发中,死锁是一个普遍存在的问题,它会在多线程并发访问共享资源时造成程序的挂起和性能下降。在本篇文章中,我们将介绍如何避免 Java 程序死锁,并提高程序性能的方法和技巧。

    21 天前
  • Web Components 中的表格组件实现

    引言 在 Web 前端开发过程中,表格组件是常见的数据展示方式之一。传统的表格实现方式多为直接使用 HTML 表格标签和 CSS 样式完成,但效果和定制性不佳。而 Web Components 的推广...

    21 天前
  • 如何使用 TypeScript 优化 Angular 项目中的性能?

    Angular 是一款流行的前端开发框架,而 TypeScript 则是一种强类型的 JavaScript 扩展语言。使用 TypeScript 能为您的 Angular 项目带来更好的类型安全和代码...

    21 天前
  • 使用 Hapi 和 Electron 构建桌面应用程序

    在过去的几年里,前端技术已经取得了快速的发展,不再局限于网页的开发,而是已经开始向桌面应用程序领域进军。其中,Hapi 和 Electron 技术组合已经慢慢成为了开发桌面应用程序的首选方案。

    21 天前
  • ECMAScript 2020 (ES11) 中的 for-await-of 使用实例

    什么是 for-await-of? 在过去,我们可能只熟悉 for 循环,其通常是用于遍历数组或对象等集合类型。但在 ES2015 中,我们引入了 for-of 循环,它可以用于遍历任何可迭代的对象,...

    21 天前
  • 如何让谷歌 Chrome 拥有更高的无障碍性?

    现代的网站需要考虑到不同用户的需求和能力。其中,无障碍性(Accessibility)是一个重要的方面,它可以使残障人士和老年人等用户更便捷地使用网站或应用。在这篇文章中,我们将探讨如何让谷歌 Chr...

    21 天前
  • ES2021:使用最佳实践进行 DOM 操作

    介绍 在 Web 开发中,操作文档对象模型(Document Object Model,简称 DOM)是经常遇到的任务之一,包括选择元素、改变元素属性或内容、添加或移除元素等。

    21 天前
  • 使用 Chai 和 Mocha 测试 React 应用程序

    随着 React 应用程序的增长和复杂性,测试变得越来越重要。Chai 和 Mocha 是两个非常流行的 JavaScript 测试框架,它们提供了一些强大的工具和函数,让我们可以更轻松和可靠地测试我...

    21 天前
  • 如何优雅地实现响应式设计?

    响应式Web设计已经成为了一个非常重要的话题。在移动设备普及的今天,很多用户都需要在PC端和移动端上使用同样的网站,并且希望它们都有好的用户体验。因此,优雅地实现响应式设计显得尤为重要。

    21 天前
  • JavaScript 新手必备:了解 ES10 新特性

    JavaScript 是现代 Web 开发中必不可少的一部分,早期 JavaScript 语法简单,标准库相对较少。但是,随着技术的发展和各种新兴库和框架的出现,JavaScript 语言逐步成为一门...

    21 天前
  • Enzyme 测试 React 组件中的异步请求

    在编写 React 组件时,我们经常会涉及到异步请求,比如获取远程 API 返回的数据并渲染到页面上。这时候如何进行测试呢?Enzyme 是 React 组件测工具库中的佼佼者,本文将介绍如何使用 E...

    21 天前
  • Mongoose 中的查询字符串详解

    Mongoose 是 MongoDB 非官方的 Object-Document Mapping(ODM)库,它在 Node.js 应用程序中对使用 MongoDB 做数据存储的操作提供了更高层次的抽象...

    21 天前
  • React 中的 Webpack 配置详解

    使用 React 开发前端应用程序时,Webpack 是必不可少的工具。它可以将你的代码打包、压缩和分离,最终将静态资源(JavaScript、CSS、图片等)打包成最终的 JavaScript 文件...

    21 天前
  • 使用 Connect 模块实现 Express.js 中的会话管理

    在开发 Web 应用程序时,会话管理是一个至关重要的部分。会话会为用户提供一个持续的登录状态,以便在一定期限内记住他们的偏好和其他信息。Express.js 是一个快速、无依赖的 Node.js We...

    21 天前
  • 利用缓存预热提高 Java 程序的性能

    在 Java 程序开发中,使用缓存可以有效提升性能。但是,虽然缓存可以减少资源的反复加载,但是第一次查询依然是需要消耗时间的。这里推荐的解决方法是:利用缓存预热,在实际使用前把数据预先加载到缓存中,从...

    21 天前
  • Redux 模式在服务器端渲染场景下的应用

    在现代 Web 应用程序开发中,服务器端渲染(SSR)已成为不可或缺的一部分,因为它可以提高应用程序的性能和可靠性。 Redux 是一种流行的状态管理方案,但在 SSR 场景下,Redux 的使用方式...

    21 天前
  • TypeScript 中的异步编程详解与命名空间的应用案例

    TypeScript 是一种由微软开发的 JavaScript 的超集。它支持 ES6+ 的语言特性和类型系统,在前端开发中广泛使用。异步编程是现代应用程序的核心,它可以提高性能、减少阻塞和提升用户体...

    21 天前
  • 使用 Docker 部署 Yii2 应用

    前言 随着现代 Web 应用的发展,容器化技术也越来越成为 Web 开发的主流方式。Docker 作为目前最成熟的容器化技术,已经被广泛应用于 Web 应用的开发和部署中。

    21 天前
  • ES2021:使用 Node.js 构建 Web 应用程序

    前言 Node.js 是一款开源的、跨平台的 JavaScript 运行环境,以其强大的可扩展性和高效的内存管理而被广泛应用于 Web 应用程序、移动应用程序、物联网应用程序等领域。

    21 天前

相关推荐

    暂无文章