前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)

虚幻大学 xuhss 466℃ 0评论

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作。进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过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、实现效果的演示

c72b1b722291ebfa0248a828d0c47033 - 前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)

3、没有封装前的代码逻辑(内附注释)


 {{ item.messageName }}

 {{
 item.createdByDeptName ? item.createdByDeptName : ''
 }}{{ item.createdAt }}

 ![]()

 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)
 })

4、封装后直接调用的全部代码片段

可以发现如果每个列表都去做上述主要的五件事情,就会有很多重复的代码,
先来看一下直接封装后写一个列表有多少代码


 <list-view :getListApi="getListApi" v-model:pageModel="pageModel" v-model:dataList="dataList">
 <item-view :dataList="dataList">item-view>
 list-view>

    <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、组件封装的代码


 <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>

  <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
最后自己每天工作中的笔记记录仓库,主要以文章链接和问题处理方案为主。

转载请注明:xuhss » 前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)

喜欢 (0)

您必须 登录 才能发表评论!