💌网络请求和分页功能的实现

核心 : 通过后端接口返回数据渲染 Vue 页面组件并实现分页功能

在现代前端开发中,网络请求和分页功能的实现是一个常见的任务。本篇文章将会介绍如何通过后端接口获取数据并在 Vue 页面组件中渲染,同时实现分页功能。我们将使用 Vue 3 作为前端框架,并结合 Pinia 进行状态管理。

💌网络请求

1
2
3
4
5
6
7
8
9
10
import hyRequest from '../request'
// 分页数据传递参数
export function getHomeHouselist(currentPage) {
return hyRequest.get({
url: "/home/houselist",
params: {
page:currentPage
}
})
}

注:这里我们使用了 hyRequest 这个封装好的 Axios 请求库,以便进行网络请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import axios from 'axios'

import { BASE_URL, TIMEOUT } from './cofig'
import useMainStote from '@/stores/modules/main'
const mainStore=useMainStote()
class hyRequest {
constructor(baseURL, timeout = 10000) {
this.instance = axios.create({
baseURL,
timeout
})
}
request(config) {
//request请求 返回promise对象
return new Promise((resolve, reject) => {
this.instance.request(config).then(res => {
resolve(res.data)
}).catch(err => {
reject(err)
})
})
}

get(config) {
return this.request({ ...config, method: "get" })
}
post(config) {
return this.request({ ...config, method: "post" })
}
}

export default new hyRequest(BASE_URL, TIMEOUT)

💌pnia存储数据

为了管理页面的状态和数据,我们使用了 Pinia,一个专为 Vue 3 设计的状态管理库。以下是如何在 Pinia 中定义和处理数据的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { getHomeHouselist } from "@/services";
import { defineStore } from "pinia";
const useHomeStore = defineStore("home", {
state: () => ({
houselist: [],
currentPage:1
}),
actions: {
// 对数据进行追加,当本页加载完后加载下一页
async fetchHouselistData() {
const res = await getHomeHouselist(this.currentPage)
// 传递的数据是数组形式 进行解构
this.houselist.push(...res.data)
this.currentPage++
}
}
})
export default useHomeStore

💌监听加载更多

  1. 因为是分页数据,所以我们需要监听下拉加载更多
  2. 我们要知道滚动的是元素,而不是窗口
  3. 所以我们需要算出窗口实际高度,
  4. 当滑到底的时候,就可以加载更多了

​ 为了理解滚动加载的原理,让我们来了解一些关键的概念:

  • scrollHeight 元素内容的高度,包括溢出的不可见内容;滚动视口高度(也就是当前元素的真实高度)
  • clientHeight 元素的像素高度,包含元素的高度+内边距,不包含水平滚动条,边框和外边距;可见区域高度
  • scrollTop “元素中的内容”超出“元素上边界”的那部分的高度;滚动条顶部到浏览器顶部高度

思路:

  1. 当scrollTop + clientHeight >= scrollHeight的时候,就说明滑到底部了,此时发送网络请求,加载下一页数据
  2. 挂载监听,卸载时移除监听,用onMounted生命周期来挂载监听,用onUnmounted生命周期移除监听

💌模式一 页面中编写

在 Vue 页面组件中,我们可以直接编写监听滚动的逻辑。以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
const scrollLirenerHandler = () => {
//变量clientHeight是可视区的高度
const clientHeight = document.documentElement.clientHeight
//变量scrollTop是滚动条滚动时,距离顶部的距离
const scrollTop = document.documentElement.scrollTop
//变量scrollHeight是滚动条的总高度
const scrollHeight = document.documentElement.scrollHeight
//滚动条到底部的条件
if (clientHeight + scrollTop >= scrollHeight) {
//加载数据事件
homeStore.fetchHouselistData()
}
}
//挂载移除监听
onMounted(() => {
window.addEventListener("scroll", scrollLirenerHandler)
})
onUnmounted(() => {
window.removeEventListener("scroll", scrollLirenerHandler)
})
</script>

💌模式二 封装方式

在项目中,通常会封装这个监听逻辑为一个函数,以便在多个页面中复用。以下是两种方式封装的示例代码:

💌方法一:传入回调函数形式

这种方式可以让你在滚动到底部时触发自定义的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { onUnmounted } from "vue"
import { onMounted } from "vue"
// 1.传入一个回调函数
export default function useScroll(reachBottonCB) {
const scrollLirenerHandler = () => {
// clientHeight 可见区域高度
const clientHeight = document.documentElement.clientHeight
// scrollTop 滚动调顶部到浏览器高度
const scrollTop = document.documentElement.scrollTop
// scrollHeight 滚动视口高度(也就是当前元素的真实高度)
const scrollHeight = document.documentElement.scrollHeight
// scrollTop + clientHeight >= scrollHeight时,说明滑到底部 此时发送网络请求,加载下一页数据
if (clientHeight + scrollTop >= scrollHeight) {
// 执行传入的回调函数
if (reachBottonCB) { reachBottonCB() }
}
}
// 用onMounted生命周期来挂载监听
onMounted(() => {
window.addEventListener("scroll", scrollLirenerHandler)
})
// 用onUNmounted生命周期移除监听
onUnmounted(() => {
window.removeEventListener("scroll", scrollLirenerHandler)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="home">
</div>
</template>
<script setup>
import useScroll from "@/hooks/useScroll"
// 在首页发起网络请求
const homeStore = useHomeStore()
homeStore.fetchHouselistData()
// 传入一个回调函数
useScroll(() => {
homeStore.fetchHouselistData()
})
</script>
<style scoped lang="less">
</style>

💌方法二:返回 ref 变量的形式

这种方式返回一个 ref 变量,用于判断是否滚动到底部,(推荐这种做法).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { onUnmounted } from "vue"
import { onMounted } from "vue"
import { ref } from "vue";
export default function useScroll() {
// 是否滑动到底部的 ref 变量
const isReachBottom = ref(false)
const scrollLirenerHandler = () => {
const clientHeight = document.documentElement.clientHeight
const scrollTop = document.documentElement.scrollTop
const scrollHeight = document.documentElement.scrollHeight
if (clientHeight + scrollTop + 1 >= scrollHeight) {
// 设置滑动到底部为 true
isReachBottom.value = true
}
}
onMounted(() => {
window.addEventListener("scroll", scrollLirenerHandler)
})
onUnmounted(() => {
window.removeEventListener("scroll", scrollLirenerHandler)
})
return { isReachBottom }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="home">
</div>
</template>
<script setup>
import useScroll from "@/hooks/useScroll"
import { watch } from 'vue';
// 在首页发起网络请求
const homeStore = useHomeStore()
homeStore.fetchHouselistData()
// 2.传参 判断
const { isReachBottom } = useScroll()
watch(isReachBottom, (newValue) => {
// 当 isReachBottom 变为 true 时触发加载更多数据的操作
if (newValue) {
homeStore.fetchHouselistData().then(() => {
// 重置 isReachBottom 为 false
isReachBottom.value=false
})
}
})
</script>
<style scoped lang="less">
</style>

💌结语:

在实现下拉加载更多数据这个功能时,碰到一个小bug,触底时并未再一次发起网络请求加载更多数据,于是log打印了下clientHeight,scrollTop,scrollHeight,发现触底时clientHeight + scrollTop 并不等于scrollHeight,scrollTop 有好几位小数点.所以为了确保滚动距离的准确性,进行了 +1 的操作才成功加载到数据。这是因为浏览器渲染滚动距离时,可能会存在小数点精度问题,导致 clientHeight + scrollTop 的值有时可能会略微小于 scrollHeight.