| 在日常的移動端開發中,經常會遇到列表的展示,以及資料量變多的情況下還會有上拉和下拉的操作。進入新公司後發現移動端好多列表,但是在看程式碼的時候發現,每個列表都是單獨的程式碼,沒有任何的封裝,都是通過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
最後自己每天工作中的筆記記錄倉庫,主要以文章連結和問題處理方案為主。