| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作。进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过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下拉刷新,上拉加载组件的封装)的更多相关文章

  1. RecyclerView下拉刷新上拉加载(三)—对Adapter的封装

    RecyclerView下拉刷新上拉加载(一) http://blog.csdn.net/baiyuliang2013/article/details/51506036 RecyclerView下拉刷 ...

  2. js 前端实现下拉刷新 上拉加载

    效果 css html,body{ height:100%; // 其他界面未设置html 无法监听scroll } /* 下拉刷新 */ .refresh-loading { transition: ...

  3. listview下拉刷新上拉加载扩展(三)-仿最新版美团外卖

    本篇是基于上篇listview下拉刷新上拉加载扩展(二)-仿美团外卖改造而来,主要调整了headview的布局,并加了两个背景动画,看似高大上,其实很简单: as源码地址:http://downloa ...

  4. 利用简洁的图片预加载组件提升h5移动页面的用户体验

    在做h5移动页面,相信大家一定碰到过页面已经打开,但是里面的图片还未加载出来的情况,这种问题虽然不影响页面的功能,但是不利于用户体验.抛开网速的原因,解决这个问题有多方面的思路:最基本的,要从http ...

  5. [深度学习] Pytorch(三)—— 多/单GPU、CPU,训练保存、加载模型参数问题

    [深度学习] Pytorch(三)-- 多/单GPU.CPU,训练保存.加载预测模型问题 上一篇实践学习中,遇到了在多/单个GPU.GPU与CPU的不同环境下训练保存.加载使用使用模型的问题,如果保存 ...

  6. 提升HTML5的性能体验系列之三 流畅下拉刷新和上拉

    下拉刷新 为实现下拉刷新功能,大多H5框架都是通过DIV模拟下拉回弹动画,在低端android手机(Android4.4以下)上,DIV动画经常出现卡顿现象(特别是图文列表的情况).解决方案还是web ...

  7. 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(6)- Bootable image格式与加载(elftosb/.bd)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Bootable image格式与加载过程. 在i.MXRT启动系列第三篇文章 Serial Down ...

  8. MVC小系列(十四)【MVC+ZTree大数据异步树加载】

    ZTree是一个jquery的树插件可以异步加载 第一步定义一个标准的接口(指的是与ztree默认的数据元素保持一致) /// <summary> /// ZTree数据结构 /// &l ...

  9. Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类

    前言 近期做换肤功能,由于换肤程度较高,受限于平台本身,实现起来较复杂,暂时搁置了该功能,但也积累了一些经验,将分两篇文章来写这部分的内容,欢迎交流! 关键字:Android动态加载 声明 欢迎转载, ...

随机推荐

  1. Solon Web 开发,十四、与Spring、Jsr330的常用注解对比

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  2. 学习Java第13天

    今天选择数据库选了半天,Oracle,MySQL,SQL sever太难了,安装了又被图形界面,Linux虚拟机所困扰 明天尽量完成数据库安装 只能说是找视频资料和安装教程了.

  3. hive 常用日期格式转换

    固定日期转换成时间戳select unix_timestamp('2016-08-16','yyyy-MM-dd') --1471276800select unix_timestamp('201608 ...

  4. 【Python爬虫】爬虫利器 requests 库小结

    requests库 Requests 是一个 Python 的 HTTP 客户端库. 支持许多 HTTP 特性,可以非常方便地进行网页请求.网页分析和处理网页资源,拥有许多强大的功能. 本文主要介绍 ...

  5. .exe文件自动重启

    echo  :杀死进程taskkill /f /im YYTWEB.exe  :等待10秒:ping 127.0.0.1 -n 10  start "" "D:\都江堰银 ...

  6. HTTP消息头(HTTP headers)-HTTP请求头与HTTP响应头

    感谢大佬:https://itbilu.com/other/relate/E1T0q4EIe.html HTTP协议将传输的信息分隔为两部分:HTTP信息头.HTTP信息体.通过HTTP头信息,使客户 ...

  7. SharedPreferences介绍

    sharedPreferences是通过xml文件来做数据存储的.         一般用来存放一些标记性的数据,一些设置信息.        使用sharedPreferences存储数据      ...

  8. Redis源码简要分析

    转载请注明来源:https://www.cnblogs.com/hookjc/ 把所有服务端文件列出来,并且标示出其作用:adlist.c //双向链表ae.c //事件驱动ae_epoll.c // ...

  9. Mac 使用自带php和Apache 安装配置Xdebug 开启本地调试模式

    Mac 安装配置php xdebug 本地调试 0.原理图 https://paper.seebug.org/308/ 测试demo构建方法 新建空白项目,目录选择Apache默认项目目录 1.下载x ...

  10. cross-env 作用

    是什么 运行跨平台设置和使用环境变量的脚本 出现原因 当您使用NODE_ENV =production, 来设置环境变量时,大多数Windows命令提示将会阻塞(报错). (异常是Windows上的B ...