发布于 2026-01-06 0 阅读
0

使用 VueJS 和 GraphQL 构建无限滚动组件 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 VueJS 和 GraphQL 构建无限滚动组件

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

很难想象2019年有哪些网页或移动应用没有至少一个信息流或列表组件。无论是新闻推送、搜索结果页面,还是标签页式的资源列表,各种分页方式都在不断涌现。其中最受欢迎的分页体验之一,就是臭名昭著、令人欲罢不能的无限滚动

在深入构建无限滚动组件之前,让我们先回答一个问题:为什么无限滚动(或者更广泛地说,分页)有用?

无限手机gif

想象一下,你正在开发一款热门新闻应用。应用存档中有数万篇文章,每天还有数十篇新文章发布。应用中的新闻推送会按发布日期对文章进行排序,以便最新文章优先显示。然而,无论是否排序,如果每次用户打开应用时都加载所有文章,那么原本的无限滚动推送就会变成无限加载推送,这会让所有人都感到沮丧。

这时,分页技术及其各种形式就派上了用场。它不会一次性加载所有新闻,而是能够快速请求——例如——最新的 25 篇文章。然后,当用户请求更多新闻时,信息流会依次获取第 26 到 50 篇文章,以此类推。这确保了响应速度快,并且不会传输过多不必要的数据。

想自己动手试代码吗?它已经在 CodeSandbox 上线了!欢迎体验。

编辑 Vue + 8base 无限滚动组件

另外……这是 GitHub 仓库。

入门

考虑到以上种种复杂情况,让我们实际使用 VueJS 和 GraphQL API 构建一个无限滚动组件。您很可能可以在任何 VueJS 项目中复用最终组件,所以完成后,不妨把它看作是您工具箱中的新工具!

先决条件

本教程假设您已具备以下条件:

  1. 对Vue略有了解
  2. 请确保您的计算机上已安装Node.js。
  3. 使用npmyarn
  4. 你对使用 GraphQL 感到兴奋!

使用 Vue 即时原型设计

在我看来,Vue 最被低估也最强大的功能就是即时原型开发。这是什么意思呢?这意味着无需搭建整个 Vue 项目,你就可以独立开发单个组件。

这将使我们能够(几乎)立即开始开发我们的组件,因此让我们在简短的设置部分中安装它。

安装 CLI 及其依赖项

首先,我们将安装 Vue CLI 和一个额外的扩展包,该扩展包使我们能够使用即时原型设计功能。

# Install Vue CLI
npm install -g @vue/cli
# Install the CLI instant prototyping
npm install -g @vue/cli-service-global

接下来,在你的电脑上创建一个新目录,名称随意。我们将在这里编写组件的所有代码。

# Create new directory
mkdir infinite-scroll-component
# Change into new directory
cd infinite-scroll-component

现在,我们要创建组件文件和一个examples目录。为什么呢?因为在开发组件时,我们需要像在完整应用程序中一样导入并与之交互。因此,这个examples目录允许我们将无限滚动组件作为本地依赖项引入,从而实现这一点。

# This will create the examples directory and all required files
mkdir examples && touch index.js infiniteScroll.vue examples/default.vue
# Your directory should look like this
tree .
=> 
infinite-scroll-component
├── examples
│   └── default.vue
├── index.js
└── infiniteScroll.vue

最后,我们需要在根目录和示例目录中初始化一个新的 NPM 项目。初始化时,在安装项目依赖项之前,只需接受所有默认设置即可。

# Init new npm project in ROOT directory
npm init
# Install dependencies
npm install --save vue graphql-request
# Change into examples directory and init new npm project
cd examples && npm init
# Require the infinite scroll component as a local dependency!
npm install --save ..

瞧!要是安装过程总是这么简单就好了……

有了即时原型功能,我们现在可以运行它vue serve FILE_NAME,它会为该单个文件启动开发服务器。试试看!你可能会……有点失望,因为我们的文件仍然是空的🙃

也就是说,在开始编写 Vue 组件之前,我们要先设置 GraphQL API。为什么呢?因为有了数据,开发过程会更有趣得多。希望你也这么认为!

在 8base 上设置 GraphQL API

搭建 GraphQL 服务器和 API 的方法有很多种。不过,我们将使用 8base,这样可以确保搭建过程极其快速且超级稳定。要开始使用,我们只需要执行以下几个步骤。

1)注册

如果您已有账户,请访问您的 8base控制面板并选择一个现有工作区。如果您还没有账户,请在8base上创建一个。他们的免费套餐足以满足我们的需求。

2)构建数据模型

在工作区中,导航至“数据构建器”页面,然后单击“+ 添加表”开始构建数据模型。我们将创建一个名为“表”的表,其中Articles包含以下字段。

文章

场地 类型 描述 选项
title 文本 文章标题 mandatory=True
body 文本 文章正文 mandatory=Truecharacters=1000
3)添加虚拟数据

让我们向数据库中添加一些虚拟记录。我已经上传了一个名为DummyData.csv 的文件。保存该文件,然后打开8base数据构建器Data中紧邻该选项卡的选项卡Schema

如何在 8base 工作区中添加 CSV 行以实现分页教程

在数据查看器的最右侧有一个下拉菜单,其中包含“导入 CSV”选项。DummyData.csv从下载文件夹中选择文件,并在弹出的对话框中确保选择“包含表头行”。您可能需要将列名映射到相应的表格字段。完成后,导入过程应该只需几秒钟。

4)角色和权限

为了允许我们的应用以适当的权限安全地访问 8base GraphQL API,我们将创建一个附加了自定义角色的 API 令牌。请导航至Settings > Roles并创建一个名为“FeedAppClientRole”的新角色。创建完成后,点击该角色以更新其权限。

在这里我们可以更新 FeedAppClientRole权限。在本例中,我们希望它能够查询/读取文章。让我们勾选/取消勾选相应的复选框来强制执行此操作。

FeedAppClientRole 权限

桌子 创造 更新 删除 田野
文章 错误的 所有记录 无记录 错误的 完全访问权限

现在我们需要将此角色关联到一个可以添加到我们应用中的 API 令牌。导航至应用商店Settings > API Tokens,添加一个新令牌,为其命名,然后在“角色”下选择我们刚刚创建的FeedAppClientRole 。

创建API令牌后请务必复制!否则您将无法再次查看。

5) 获取工作区 API 端点

最后,我们来复制工作区的 API 端点。这个端点是我们工作区独有的,我们将把所有用于创建新记录的 GraphQL 查询都发送到这里。

获取端点的方法有几种。不过,只需导航到工作区主页,即可在左下角找到端点。

6)测试它是否有效!

在继续之前,我们应该先测试一下 API 是否设置正确。你可能会问,该怎么做呢?当然是查询它啦!与其设置或使用 GraphQL 客户端,不如直接在终端运行一个传统的 curl 命令,然后查看响应结果。

请务必将 ` <YOUR_API_ENDPOINT><workspace API endpoint>` 替换为您的工作区 API 端点,并将`<API Token>` 替换<YOUR_API_TOKEN>为您创建的 API Token。

curl -X POST '<YOUR_API_ENDPOINT>' \
     -H "Content-Type: application/json" \
     -H 'Authorization: Bearer <YOUR_API_TOKEN>' \
     -d '{ "query": "{ articlesList(first: 10) { items { title } } }"}'

JSON 响应是否显示了文章标题列表?太棒了!干得漂亮!我们现在可以继续前进,开始创建组件了。

设置组件

那么,让我们快速列出无限滚动组件需要实现的功能。这样我们就能更清晰地思考需要采取的步骤。

简易规格

  • 查询 GraphQL 端点以获取N条记录。
  • 允许用户在渲染后的列表中垂直滚动。
  • 识别用户何时到达列表末尾。
  • 查询另外N条记录并将它们添加到列表中。
  • 允许开发者为列表项指定模板。

牢记以上几点,让我们在文件中添加一些代码,以便我们有一个可以操作的结构。

examples/default.vue

再次强调,我们之所以需要examples/default.vue文件,是为了能够像在完整应用程序中一样导入正在开发的组件。现在运行命令vue serve examples/default.vue——或者vue serve default.vue,如果您已经在 examples 目录中,则运行命令。这将启动即时原型开发服务器。在进行增量文件更新时,您可能会看到一些错误;暂时忽略它们即可。

根据我们简单的规范,我们需要一个无限滚动组件,该组件可以从 GraphQL API 获取指定数量的记录。此外,我们还希望能够指定一个模板,该模板将为获取的每条记录进行渲染。

考虑到这一点,让我们创建一个示例来演示如何使用我们的组件。务必阅读代码中的注释!

<style scoped>
* {
  font-family: Arial, Helvetica, sans-serif;
}

.container {
  margin: 0 auto;
  width: 420px;
}
</style>

<template>
    <!-- 
      Here's our InfiniteScroll component. We want to pass it some simple props so that the component knows... 

      1) query: The GraphQL query to run. 
      2) limit: How many records to fetch.
      3) respKey: A key for accessing the response.
      4) endpoint: Where to fetch the records from.
      5) authToken: If needed, a token to access the API.
     -->
     <section class="container"> 
      <InfiniteScroll 
          :query="query"
          :limit="limit" 
          :respKey="respKey" 
          :endpoint="endpoint" 
          :authToken="authToken">
          <!-- 
            Instead of being stuck with a generic template, we want to be able to render out each record that gets fetched with a
            custom template. 

            1) Using v-slot we can name the scoped data that's passed to the template.
            2) The template is a child component of InfiniteScrollm so we can access it using <slot />
          -->
          <template v-slot="item">
              <!-- 
                Using the scoped slot data, we're creating a simple template that will render out the wanted data from our fetched records.
                -->
              <article>
                <h4>{{ item.title }}</h4>
                <p>{{ item.body }}</p>
              </article>
          </template>
      </InfiniteScroll>
    </section>
</template>

<!-- 
  Next up... <script> will go here 
-->

太棒了!我们基本上只是把组件的使用方式写了出来InfiniteScroll。看起来很直观,对吧?现在我们得真正把它构建出来……不过在此之前,让我们先<script>examples/default.vue文件中添加标签,这样所有命名的数据值就都存在了。

只需将以下代码放在<template>标签和注释的正下方即可!

<script>
/**
 * We've got to import our infinite scroll component! 
 */
import { InfiniteScroll } from 'infinite-scroll-component';

export default {
    /**
     * Registering the component will allow us to
     * use it in our template, as is shown above.
     */ 
    components: {
        InfiniteScroll
    },
    data() {
        return {
            /**
             * Here we've adding the values to that are
             * getting passed to the InfiniteScroll
             * comonent. They could be directly added in, 
             * the template, though are better organized
             * in the data method like this.
             */
            limit: 25,
            /**
             * Depending on the API, the returned records may be
             * nested in the response object. This key is optional
             * though can be used for accessing a nested list.
             */
            respKey: 'articlesList.items',
            /**
             * Optional auth token, depending on GraphQL API
             * REPLACE IT WITH YOUR API TOKEN
             */ 
            authToken: 'YOUR API TOKEN',
            /**
             * Required GraphQL API endpoint from where records get fetched.
             * REPLACE IT WITH YOUR WORKSPACE API ENDPOINT
             */
            endpoint: 'YOUR WORKSPACE API ENDPOINT',
            /**
             * Required GraphQL query for fetching records. This query
             * is designed for our 8base API and will return the paginated
             * results from our articles table.
             */
            query: `query($limit: Int, $skip: Int) {
                articlesList(first: $limit, skip: $skip) {
                    items {
                        id
                        title
                        body
                    }
                }
            }`
        }
    }
};
</script>

干得漂亮!我们的examples/default.vue组件已设置完毕。请确保您已将endpoint`and`值更新apiToken为工作区中的相应值。

infiniteScroll.vue

现在到了实际InfiniteScroll组件部分。和上一个组件类似,我们先从 `<div>`<template>和 `<span> <style>` 标签开始。无限滚动确实需要一些功能性样式。

<style scoped>
/**
 * Some of the styling is functional, while other
 * is aesthetic. Feel free to play with it!
 */
section {
  overflow-y: scroll;
  height: 500px;
}
</style>

<template>
  <!-- 
    Component container with scroll event listener
    for triggering handle scroll event.
  -->
  <section @scroll="handleScroll">
    <!--
      For every item in the items array, render
      the slotted template and bind the item data.
     -->
    <slot v-for="item in items" v-bind="item" />
  </section>
</template>

<!-- 
  Next up... <script> will go here 
-->

我知道。这简直简单得令人抓狂,对吧?但是,为什么要把事情搞得比实际需要的更复杂呢?我们只想为从 API 获取的每条记录创建一个模板,并知道何时需要获取更多记录。就是它的作用。

现在,让我们添加一个<script>标签,让一切真正发挥作用。

<script>
/* eslint-disable no-console */

/* Imports the graphQL request client */
import { GraphQLClient } from "graphql-request";

export default {
  /**
   * Declare the props expected to be passed from
   * any parent component (the ones in Dev.vue).
   */
  props: {
    query: {
      type: String,
      required: true
    },
    respKey: {
      type: String,
      default: ""
    },
    limit: {
      type: Number,
      default: 25
    },
    endpoint: {
      type: String,
      required: true
    },
    authToken: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      /* The array for storing all records fetched */
      items: [],
      /**
       * Configure the GraphQL Client, setting headers
       * only if the authTokenis specified.
       */
      client: new GraphQLClient(
        this.endpoint,
        this.authToken
          ? {
              headers: {
                authorization: `Bearer ${this.authToken}`
              }
            }
          : null
      )
    };
  },
  computed: {
      respKeyParser() {
          return this.respKey.split('.')
      }
  },
  methods: {
    /**
     * Callback for the onscroll event checks whether the scroll position
     * is near the bottom of the scroll container.
     */
    handleScroll({ target: { scrollTop, clientHeight, scrollHeight } }) {
      if (scrollTop + clientHeight >= scrollHeight) this.loadBatch();
    },
    /**
     * When a new batch of articles are retrieved from the API,
     * add them to the items.
     */
    handleLoad(response) {
      if (this.respKey) {
          response = this.respKeyParser.reduce((o, v) => o[v], response)
      }
      this.items = this.items.concat(response);
    },
    /**
     * Use the client to send query to GraphQL API
     * with the needed variables, 'limit' and 'skip'.
     */
    loadBatch() {
      this.client
        .request(this.query, {
          limit: this.limit,
          skip: this.items.length
        })
        .then(this.handleLoad)
        .catch(console.error);
    }
  },
  /**
   * When the component mounts (first renders), load the
   * initial batch of posts.
   */
  mounted() {
    this.loadBatch();
  }
};
</script>

当然,这部分内容比其他部分要多一些。不过,真正值得一提的只有几点;其余部分应该都包含在代码文档中。

首先,我们初始化客户端,并根据是否传递了参数来GraphQLClient有条件地传递它。初始化后的客户端将在方法中用于执行对我们 API 的 GraphQL 调用。它使用必需的prop,该 prop 接收参数变量。headersauthTokenloadBatchqueryskiplimit

` skipand`limit变量是articlesList查询处理分页所必需的。`and`limit仅表示每次请求要加载多少条记录,而 `and`skip指定已加载的记录数,或者要从列表中的哪个索引进行切片。因此,如果我们最初使用 ` and` 从 API获取记录 `1` AB`2` 和 `3` ,然后在下一个请求中指定 ` and` ,我们将收到记录 ` 1` 、 `2` 和 `3` Climit = 3, skip = 0limit = 3, skip = 3DEF

最后,我们来看一下这个handleScroll方法。这是事件的回调方法@scroll。通过解包传入的event参数,我们可以访问 `h` scrollTopclientHeight`h` 和 ` scrollHeighth` 的值。`h`clientHeight是一个固定值,表示可滚动元素的高度(以像素为单位)。而 `h`scrollTop会在每次滚动事件发生时改变,表示从滚动容器顶部到当前位置的距离。

如果clientHeight加号scrollTop大于或等于scrollHeight(元素的可滚动高度,以像素为单位),则我们知道容器已完全滚动!

使用 8base GraphQL 后端的分页无限滚动组件

index.js

想知道为什么您的组件没有出现在浏览器(http://localhost:8080)中吗?我们没有导出它!

index.js请将以下内容更新到文件中:

import InfiniteScroll from './infiniteScroll.vue';

export { InfiniteScroll }

总结一下,还有一些其他有趣的事情

我们的分页组件已经完成!现在它可以与任何使用 GraphQL API 的 Vue 项目一起使用。该组件应该会在浏览器中渲染出记录。如果没有,请检查错误信息,并告诉我是否有什么异常!

此外,如果您对构建完整的分页组件(带导航的标签页)而不是无限滚动感兴趣,请查看Denny Hendrick撰写的这篇关于 Vue.js 分页的文章

综上所述,这里是本教程的 GitHub 代码库,其中包含示例。

文章来源:https://dev.to/sebastian_scholl/building-an-infinite-scroll-component-using-vuejs-and-graphql-aih