前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)
| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作。进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过vant组件,里面充满了过多的重复代码,在有bug或者有需求变更的时候,每次的改动都要对很多个相同逻辑的页面组件进行修改,于是花了一点时间,将其进行封装,发现还是节省了很多的时间。自己做一个记录。
前端提升生产力系列文章
1.前端提升生产力系列一(vue3 element-plus 配置json快速生成form表单组件)
2.前端提升生产力系列二(vue3 element-plus 配置json快速生成table列表组件)
3.前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)
本文涉及所有源代码已上传 https://github.com/aehyok/vue-qiankun
1、实现功能的讲解
先说一下实现的功能
- 1、模拟了一个api请求,用于请求接口数据的,并将请求设置为5秒后数据请求成功(效果明显一点)
- 2、定义请求接口的页码相关参数,以及控制逻辑
- 3、下拉刷新第一页数据,并且在刷新过程中,不能再进行下拉刷新
- 4、上拉加载下一页数据,并且在加载过程中,不能再进行上拉加载
- 5、加载到最后一页,则最末端会显示【数据已加载完毕】
- 6、如果请求api一开始就没有数据,则显示成一个默认图片(代表没有加载到数据)
2、实现效果的演示
3、没有封装前的代码逻辑(内附注释)
<template>
<van-pull-refresh
v-model="isRefresh"
@refresh="refreshClick"
loading-text="正在请求数据"
success-text="数据刷新成功"
>
<van-list
v-model:loading="isListLoading"
:finished="isFinished"
:offset="state.offset"
finished-text="数据已加载完毕"
:immediate-check="false"
@load="onLoad"
>
<div class="main">
<div class="flex" v-for="item in dataList" :key="item.id">
<div :class="!item.url ? 'itemCollagen' : 'itemCollagenSeventy'">
<p>{{ item.messageName }}</p>
<span
><span :class="item.createdByDeptName ? 'createdByDeptName' : ''">{{
item.createdByDeptName ? item.createdByDeptName : ''
}}</span
>{{ item.createdAt }}</span
>
</div>
<div v-if="item.url">
<img :src="item.url" alt="" />
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
<div v-if="state.nodata===true"><van-empty description="没有数据" /></div>
</template>
<script setup>
import { PullRefresh as VanPullRefresh, List as VanList, Empty as VanEmpty } from 'vant';
import { onBeforeMount, ref, reactive, watch } from 'vue';
const setTotal = 51 // 设置列表总记录数
let dbList = [] // 通过循环向数组插入测试数据
for(let i= 0; i< setTotal; i++) {
dbList.push({
id: i + 1,
messageName: '长图片'+(i+1),
createdAt: '2021-07-27 17:06:19',
createdByDeptName: '百色',
url: 'http://vue.tuokecat.com/cdn/h5/newslist.jpg',
})
}
const successText = ref('正在请求数据')
const dataList = ref([]);
const pageModel = reactive({
page: 1,
limit: 15,
total: 0,
pages: 0,
});
const sleep = (time) => {
return new Promise(resolve => setTimeout(resolve, time))
}
/**
* 模拟通过api获取第几页的数据
* @param {每页多少条记录} limit
* @param {第几页} page
*/
const getListApi = async(limit, page) => {
let start = limit * (page - 1);
let end = limit * page;
let tempList = dbList.slice(start, end);
console.log(pageModel,tempList, `获取第${page}页数据列表`);
const result = {
code: 200,
message: 'success',
data: {
docs: tempList,
page: page,
limit: limit,
total: setTotal,
pages: Math.ceil(setTotal / 15)
}
}
await sleep(5000)
return new Promise(resolve => resolve(result))
};
const state = {
offset: 6, // 滚动条与底部距离小于 offset 时触发load事件
nodata: false,
};
// 控制下拉刷新的状态,如果为true则会显示,则为一直处于加载中,到请求接口成功手动设置false,则代表刷新成功
const isRefresh = ref(false);
// 可以判断如果是上拉加载的最后一页的时候,加载成功设置为true,再上拉则不会进行加载了
const isFinished = ref(false);
// 是否在加载过程中,如果是true则不会继续出发onload事件
const isListLoading = ref(false);
onBeforeMount(() => {
getList()
});
// 下拉刷新列表
const refreshClick = () => {
isRefresh.value = true;
isFinished.value = false;
isListLoading.value = true;
// 通过接口调用数据
console.log('调用接口成功,并重置页码为1');
successText.value="正在加载数据"
pageModel.page = 1;
getList()
};
//上拉加载下一页
const onLoad = () => {
// 判断当前页码+1 是否大于总页数
// 大于总页数,结束加载,反之继续请求
isListLoading.value = true
if (pageModel.page + 1 > pageModel.pages) {
isFinished.value = true
isListLoading.value = false
console.warn('数据页面已超出最大页,不能再进行请求了')
return;
} else {
pageModel.page = pageModel.page + 1;
getList()
}
};
const getList = () => {
getListApi(pageModel.limit,pageModel.page).then(result => {
console.log(result, 'ssssssssssssss')
successText.value="1111111111"
let tempList = result.data.docs
pageModel.pages = result.data.pages
pageModel.total = result.data.total
isListLoading.value = false
isRefresh.value = false
if (pageModel.page === 1) {
dataList.value = tempList
} else {
dataList.value=[...dataList.value, ...tempList]
}
})
};
watch(()=> pageModel.total, (newValue, oldValue) => {
console.log('watch', newValue> 0, oldValue)
state.nodata = !(newValue > 0)
})
</script>
4、封装后直接调用的全部代码片段
可以发现如果每个列表都去做上述主要的五件事情,就会有很多重复的代码,
先来看一下直接封装后写一个列表有多少代码
<template>
<list-view :getListApi="getListApi" v-model:pageModel="pageModel" v-model:dataList="dataList">
<item-view :dataList="dataList"></item-view>
</list-view>
</template>
<script lang="ts" setup>
import listView from '../../components/list/index.vue';
import itemView from './item-view.vue';
import {reactive, ref } from 'vue';
import type {PageModel } from '../../types/models/index.d';
const dataList = ref([]);
const pageModel = reactive<PageModel>({
page: 1,
limit: 15,
total: 0,
pages: 0,
});
const setTotal = 51 // 设置列表总记录数
let dbList: any= [] // 通过循环向数组插入测试数据
for (let i = 0; i < setTotal; i++) {
dbList.push({
id: i + 1,
messageName: '长图片' + (i + 1),
createdAt: '2021-07-27 17:06:19',
createdByDeptName: '百色',
url: 'http://vue.tuokecat.com/cdn/h5/newslist.jpg',
})
}
/**
* 模拟通过api获取第几页的数据
* @param {每页多少条记录} limit
* @param {第几页} page
*/
const getListApi = async (limit, page) => {
let start = limit * (page - 1);
let end = limit * page;
let tempList = dbList.slice(start, end);
console.log(pageModel, tempList, `获取第${page}页数据列表`);
const result = {
code: 200,
message: 'success',
data: {
docs: tempList,
page: page,
limit: limit,
total: setTotal,
pages: Math.ceil(setTotal / 15)
}
}
const sleep = (time) => {
return new Promise(resolve => setTimeout(resolve, time))
}
await sleep(1000)
return new Promise(resolve => resolve(result))
};
</script>
解析以上代码:
1、最重要便是list-view是我们封装的组件,只需要引用即可
2、而item-view则是我们列表中每一项的UI视图布局和样式,相当于抽离出来了。跟原来一模一样
3、主要是准备模拟api请求多了不少代码
4、声明变量和一些定义
封装的理念
- 1、将尽可能通用的代码,抽离出来,不用再进行复制粘贴的操作
- 2、修改维护逻辑时只需要修改一个地方即可,无需每个列表都要修改
- 3、每次写出来的列表bug少,效率高
5、组件封装的代码
<template>
<van-pull-refresh
v-model="isRefresh"
@refresh="refreshClick"
:loading-text="'正在请求数据'"
success-text="数据刷新成功"
>
<van-list
v-model:loading="isListLoading"
:finished="isFinished"
:offset="state.offset"
finished-text="数据已加载完毕"
:immediate-check="false"
@load="onLoad"
>
<slot></slot>
</van-list>
</van-pull-refresh>
<div v-if="state.nodata === true">
<van-empty description="没有数据" />
</div>
</template>
<script lang="ts" setup>
import { PullRefresh as VanPullRefresh, List as VanList, Empty as VanEmpty } from 'vant';
import { onBeforeMount, ref, PropType, watch } from 'vue';
import { PageModel } from '/@/types/models';
const emits = defineEmits(['getList', 'update:pageModel', 'update:dataList']);
const props = defineProps({
pageModel: {
type: Object as PropType<PageModel>,
default: () => { },
},
dataList: {
type: [Array],
default: () => []
},
getListApi: {
type: Function,
default: () => { }
}
});
const state = {
offset: 6, // 滚动条与底部距离小于 offset 时触发load事件
nodata: false,
};
// 控制下拉刷新的状态,如果为true则会显示,则为一直处于加载中,到请求接口成功手动设置false,则代表刷新成功
const isRefresh = ref(false);
// 可以判断如果是上拉加载的最后一页的时候,加载成功设置为true,再上拉则不会进行加载了
const isFinished = ref(false);
// 是否在加载过程中,如果是true则不会继续出发onload事件
const isListLoading = ref(false);
onBeforeMount(() => {
// emits('getList');
console.log('getList')
getList()
});
const refreshClick = () => {
isRefresh.value = false;
isFinished.value = false;
isListLoading.value = true;
// 通过接口调用数据
console.log('调用接口成功');
props.pageModel.page = 1;
emits('update:pageModel', props.pageModel)
// emits('getList');
getList()
};
const onLoad = () => {
isListLoading.value = true
if (props.pageModel.page + 1 > props.pageModel.pages) {
isFinished.value = true
isListLoading.value = false
console.warn('数据页面已超出最大页,不能再进行请求了')
return;
} else {
props.pageModel.page = props.pageModel.page + 1;
}
emits('update:pageModel', props.pageModel)
// emits('getList');
getList()
};
const dataList: any = ref([]);
const getList = () => {
props.getListApi(props.pageModel.limit, props.pageModel.page).then((result: any) => {
console.log(result, 'ssssssssssssss')
let tempList: [] = result.data.docs
props.pageModel.limit = result.data.limit
props.pageModel.page = result.data.page
props.pageModel.pages = result.data.pages
props.pageModel.total = result.data.total
isListLoading.value = false
isRefresh.value = false
if (props.pageModel.page === 1) {
dataList.value = tempList
} else {
dataList.value = [...props.dataList, ...tempList]
}
emits('update:dataList', dataList.value)
emits('update:pageModel', props.pageModel)
})
};
// 判断是否有数据
watch(() => props.pageModel.total, (newValue, oldValue) => {
console.log('watch', newValue > 0, oldValue)
state.nodata = !(newValue > 0)
})
</script>
- 解析封装的代码
- 1、通过watch 监测tatal,判断是否有数据,来确定是否要显示没有数据时的默认图片
- 2、将请求通过props进行传递,在封装的组件中进行统一处理,当然这里就要要求使用组件的接口要统一参数
- 3、请求数据后要将数据列表和分页数据通过emits进行回传父组件,用于显示列表数据
- 4、下拉刷新判断仍然存在统一封装
- 5、上拉加载列表数据判断仍热存在统一封装
- 6、最后一次加载数据进行判断处理
- 7、TypeScript用的还不够熟练,数据列表这一块的封装还不到位,争取有时间再进行深入一下。
总结
- 实际使用过程中还可以继续优化很多的细节工作,比如有些列表一次性加载即可,不需要进行下拉刷新或者上拉加载的功能,都可以通过传递参数进行控制等等。
- 封装的过程就是对那些重复性的工作进行提炼,提高代码的复用性,减少代码的拷贝粘贴,这样调用组件后的代码也方便维护和测试工作,相对来说稳定性也更加强劲。
https://github.com/aehyok/vue-qiankun/vite-vue+react+demo/vite-h5/src/views/news-list/
本文中涉及到的代码链接,其中的news-before是没有封装的代码,news-after则是封装后的代码。
https://github.com/aehyok/2022
最后自己每天工作中的笔记记录仓库,主要以文章链接和问题处理方案为主。
前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)的更多相关文章
- RecyclerView下拉刷新上拉加载(三)—对Adapter的封装
RecyclerView下拉刷新上拉加载(一) http://blog.csdn.net/baiyuliang2013/article/details/51506036 RecyclerView下拉刷 ...
- js 前端实现下拉刷新 上拉加载
效果 css html,body{ height:100%; // 其他界面未设置html 无法监听scroll } /* 下拉刷新 */ .refresh-loading { transition: ...
- listview下拉刷新上拉加载扩展(三)-仿最新版美团外卖
本篇是基于上篇listview下拉刷新上拉加载扩展(二)-仿美团外卖改造而来,主要调整了headview的布局,并加了两个背景动画,看似高大上,其实很简单: as源码地址:http://downloa ...
- 利用简洁的图片预加载组件提升h5移动页面的用户体验
在做h5移动页面,相信大家一定碰到过页面已经打开,但是里面的图片还未加载出来的情况,这种问题虽然不影响页面的功能,但是不利于用户体验.抛开网速的原因,解决这个问题有多方面的思路:最基本的,要从http ...
- [深度学习] Pytorch(三)—— 多/单GPU、CPU,训练保存、加载模型参数问题
[深度学习] Pytorch(三)-- 多/单GPU.CPU,训练保存.加载预测模型问题 上一篇实践学习中,遇到了在多/单个GPU.GPU与CPU的不同环境下训练保存.加载使用使用模型的问题,如果保存 ...
- 提升HTML5的性能体验系列之三 流畅下拉刷新和上拉
下拉刷新 为实现下拉刷新功能,大多H5框架都是通过DIV模拟下拉回弹动画,在低端android手机(Android4.4以下)上,DIV动画经常出现卡顿现象(特别是图文列表的情况).解决方案还是web ...
- 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(6)- Bootable image格式与加载(elftosb/.bd)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Bootable image格式与加载过程. 在i.MXRT启动系列第三篇文章 Serial Down ...
- MVC小系列(十四)【MVC+ZTree大数据异步树加载】
ZTree是一个jquery的树插件可以异步加载 第一步定义一个标准的接口(指的是与ztree默认的数据元素保持一致) /// <summary> /// ZTree数据结构 /// &l ...
- Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类
前言 近期做换肤功能,由于换肤程度较高,受限于平台本身,实现起来较复杂,暂时搁置了该功能,但也积累了一些经验,将分两篇文章来写这部分的内容,欢迎交流! 关键字:Android动态加载 声明 欢迎转载, ...
随机推荐
- Solon Web 开发,五、数据访问、事务与缓存应用
Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...
- 新手学习Java,如何快速从入门到精通!
首先我要给你说的是,任何一个东西从零到精通,都是需要一个过程的,就相当于我们在小学的时候学乘法口诀表,背文言文一样,都是需要我们慢慢的去渡过这个阶段,我们才能慢慢的掌握,其次,毕竟这是一门编程语言,你 ...
- 读书笔记http之第一章
http TCP/IP协议各层: 应用层 决定了向用户提供应用服务时通信的活动. 比如 : FTP(FileTransferProtocol,文件传输协议)和DNS(DomainNameSystem, ...
- openssl基本原理 + 生成证书(转)
https://blog.csdn.net/cpcpcp123/article/details/108885922 https://liruzhen.blog.csdn.net/article/det ...
- linux可用内存判断
free是完全没有占用的空闲内存,Available 减 free是操作系统为了优化运行速度拿来调用的内存, 程序需要的话操作系统会进行释放.所以一般看Available即可. free+buffer ...
- ApacheCN 计算机视觉译文集 20211110 更新
OpenCV3 和 Qt5 计算机视觉 零.前言 一.OpenCV 和 Qt 简介 二.创建我们的第一个 Qt 和 OpenCV 项目 三.创建一个全面的 Qt + OpenCV 项目 四.Mat和Q ...
- ApacheCN 深度学习译文集 20210112 更新
新增了六个教程: TensorFlow 2 和 Keras 高级深度学习 零.前言 一.使用 Keras 入门高级深度学习 二.深度神经网络 三.自编码器 四.生成对抗网络(GAN) 五.改进的 GA ...
- Uwl.Admin.Core开源框架(三) 使用RabbitMQ
Uwl.Admin.Core中使用RabbitMQ消息队列: 本文负责讲解RabbitMQ的使用 Uwl.Admin.Core使用的技术有: *.Async和Await 异步编程 *.Reposito ...
- kafka经验
1.kafka集群搭建 解压 配环境变量 修改配置文件 2.编写管理脚本去管理集群 3.kafka常用命令 4.kafka监控 5.分享kafka经验
- Java中HttpURLConnection使用详解、总结。
感谢大佬:https://blog.csdn.net/qq_40036754/article/details/102554755 文章目录 一.前言 二.HttpURLConnection 介绍 三. ...