| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作。进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过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. 【Android】安卓中的存储

    [Android]安卓中的存储 1.存储在App内部 最简单的一种.在尝试过程中发现,手机中很多文件夹都没有权限读写.我们可以将我们需要写的文件存放到App中的files文件夹中,当然我们有权限在整个 ...

  2. android+opencv+opencl: cv::dft()的opencl版本的性能分析

    在小米mix 2s + 高通骁龙 845 + Adreno 630 上测试了opencl版本的cv::dft(). 测试数据 先看表格里面的描述: 名称 函数名 最大时间(ms) 平均时间(ms) 说 ...

  3. 主键约束(primary key 简称PK)

    7.5.主键约束 主键约束相关术语 主键约束 主键字段:字段添加了主键约束,叫主键字段 主键值:主键字段中的每个值都叫主键值 什么是主键? 主键值是每一行记录的唯一标识(主键值是每一行记录的身份证号) ...

  4. 用shell脚本写出检测/tmp/size.log文件,如果存在显示它的内容,不存在则创建一个文件将创建时间写入

    1 #!/bin/bash 2 if [ -d "/tmp" ]; then 3 echo "/tmp is exists" 4 else 5 mkdir /t ...

  5. 扩容新生代为什么能够提高GC的效率

    扩容新生代为什么能够提高GC的效率 该文章默认读者对JVM的基础有所了解 在学习JVM的时候,遇到了个人感觉比较有意思的问题,通过视频学习整理了一下. 先来上图: 大部分情况下,对象都会进入Eden区 ...

  6. Android开发----WebView&Activity生命周期

    WebView webview是一个再应用中设置好位置和大小的浏览器,而且不会放置任何花哨的UI. 在大多数情况下,除非你调用了原生API,否则不必在webview中专门测试web应用. 首先为Web ...

  7. linux磁盘管理(全面解析)

    目录 一:磁盘管理 1.磁盘管理作用 2.磁盘挂载顺序 3.磁盘分区 4.Linux 磁盘管理常用命令 5.磁盘分区内容 二:linux中分区的意义 三:分区的步骤与顺序 1.添加磁盘 2.查看创建新 ...

  8. IDEA出现Cannot resolve symbol “xxx“(无法解析符号)的解决办法

    1,File->Invalidate Caches/Restart 清除缓存并重启 idea 2,检查pom文件中的依赖关系是否正确 3,maven -> Reimport 4,打开pro ...

  9. 微服务架构 | 7.2 构建使用 JWT 令牌存储的 OAuth2 安全认证

    目录 前言 1. JWT 令牌存储基础知识 1.1 JSON Web Token 2. 构建使用 JWT 令牌存储的 OAuth2 服务器 2.1 引入 pom.xml 依赖文件 2.2 创建 JWT ...

  10. MybatisPlus多表连接查询

    一.序言 (一)背景内容 软件应用技术架构中DAO层最常见的选型组件为MyBatis,熟悉MyBatis的朋友都清楚,曾几何时MyBatis是多么的风光,使用XML文件解决了复杂的数据库访问的难题.时 ...