使用 VueJS 和 GraphQL 构建无限滚动组件
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
很难想象2019年有哪些网页或移动应用没有至少一个信息流或列表组件。无论是新闻推送、搜索结果页面,还是标签页式的资源列表,各种分页方式都在不断涌现。其中最受欢迎的分页体验之一,就是臭名昭著、令人欲罢不能的无限滚动。
在深入构建无限滚动组件之前,让我们先回答一个问题:为什么无限滚动(或者更广泛地说,分页)有用?
想象一下,你正在开发一款热门新闻应用。应用存档中有数万篇文章,每天还有数十篇新文章发布。应用中的新闻推送会按发布日期对文章进行排序,以便最新文章优先显示。然而,无论是否排序,如果每次用户打开应用时都加载所有文章,那么原本的无限滚动推送就会变成无限加载推送,这会让所有人都感到沮丧。
这时,分页技术及其各种形式就派上了用场。它不会一次性加载所有新闻,而是能够快速请求——例如——最新的 25 篇文章。然后,当用户请求更多新闻时,信息流会依次获取第 26 到 50 篇文章,以此类推。这确保了响应速度快,并且不会传输过多不必要的数据。
想自己动手试代码吗?它已经在 CodeSandbox 上线了!欢迎体验。
入门
考虑到以上种种复杂情况,让我们实际使用 VueJS 和 GraphQL API 构建一个无限滚动组件。您很可能可以在任何 VueJS 项目中复用最终组件,所以完成后,不妨把它看作是您工具箱中的新工具!
先决条件
本教程假设您已具备以下条件:
使用 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=True,characters=1000 |
3)添加虚拟数据
让我们向数据库中添加一些虚拟记录。我已经上传了一个名为DummyData.csv 的文件。保存该文件,然后打开8base数据构建器Data中紧邻该选项卡的选项卡。Schema
在数据查看器的最右侧有一个下拉菜单,其中包含“导入 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` A、B`2` 和 `3` ,然后在下一个请求中指定 ` and` ,我们将收到记录 ` 1` 、 `2` 和 `3` 。Climit = 3, skip = 0limit = 3, skip = 3DEF
最后,我们来看一下这个handleScroll方法。这是事件的回调方法@scroll。通过解包传入的event参数,我们可以访问 `h` scrollTop、clientHeight`h` 和 ` scrollHeighth` 的值。`h`clientHeight是一个固定值,表示可滚动元素的高度(以像素为单位)。而 `h`scrollTop会在每次滚动事件发生时改变,表示从滚动容器顶部到当前位置的距离。
如果clientHeight加号scrollTop大于或等于scrollHeight(元素的可滚动高度,以像素为单位),则我们知道容器已完全滚动!
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


