💌网络请求和分页功能的实现
核心 : 通过后端接口返回数据渲染 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) { 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
|
💌监听加载更多
- 因为是分页数据,所以我们需要监听下拉加载更多
- 我们要知道滚动的是元素,而不是窗口
- 所以我们需要算出窗口实际高度,
- 当滑到底的时候,就可以加载更多了
为了理解滚动加载的原理,让我们来了解一些关键的概念:
- scrollHeight 元素内容的高度,包括溢出的不可见内容;滚动视口高度(也就是当前元素的真实高度)
- clientHeight 元素的像素高度,包含元素的高度+内边距,不包含水平滚动条,边框和外边距;可见区域高度
- scrollTop “元素中的内容”超出“元素上边界”的那部分的高度;滚动条顶部到浏览器顶部高度
思路:
- 当scrollTop + clientHeight >= scrollHeight的时候,就说明滑到底部了,此时发送网络请求,加载下一页数据
- 挂载监听,卸载时移除监听,用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"
export default function useScroll(reachBottonCB) { const scrollLirenerHandler = () => { const clientHeight = document.documentElement.clientHeight const scrollTop = document.documentElement.scrollTop const scrollHeight = document.documentElement.scrollHeight if (clientHeight + scrollTop >= scrollHeight) { if (reachBottonCB) { reachBottonCB() } } } onMounted(() => { window.addEventListener("scroll", scrollLirenerHandler) }) 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() { 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) { 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()
const { isReachBottom } = useScroll() watch(isReachBottom, (newValue) => { if (newValue) { homeStore.fetchHouselistData().then(() => { isReachBottom.value=false }) } }) </script> <style scoped lang="less"> </style>
|
💌结语:
在实现下拉加载更多数据这个功能时,碰到一个小bug,触底时并未再一次发起网络请求加载更多数据,于是log打印了下clientHeight,scrollTop,scrollHeight
,发现触底时clientHeight + scrollTop
并不等于scrollHeight
,scrollTop
有好几位小数点.所以为了确保滚动距离的准确性,进行了 +1
的操作才成功加载到数据。这是因为浏览器渲染滚动距离时,可能会存在小数点精度问题,导致 clientHeight + scrollTop
的值有时可能会略微小于 scrollHeight
.