Vue + Vant 移动端列表组件实现
前言
列表组件封装得越多,Bug就藏得越深,不如把组件拆成几个页面算了!教你高大上封装移动端的list组件
实现思路
- 在
setup函数中使用ref和reactive创建响应式变量,如listData存储列表数据,page存储当前页码等。 - 在组件挂载时通过
requestData函数请求第一页数据,将数据存入listData中。 - 监听下拉刷新和底部加载事件,在事件处理函数中调用
requestData函数请求数据,根据请求结果更新相应的变量和数据。 - 在模板中使用 Vant 组件库中的
van-list和van-pull-refresh组件进行页面渲染。
组件代码
<template>
<van-pull-refresh
style="min-height: 100vh"
v-model="isRefreshing"
@refresh="handleRefresh"
>
<van-list
v-model="isLoading"
:finished="isFinished"
finished-text="没有更多了"
@load="handleLoad"
>
<slot name="list-item" :items="listData" />
</van-list>
<van-empty
v-if="!isLoading && listData.length === 0"
description="暂无数据"
/>
<van-cell
v-if="!isLoading && isLoadFailed"
title="加载失败"
is-link
@click="handleLoad"
/>
</van-pull-refresh>
</template>
<script>
import axios from "axios";
import { ref, reactive } from "vue";
import { showToast } from "vant";
export default {
props: {
api: { type: String, required: true },
dataKey: { type: String, default: "data" },
pageSize: { type: Number, default: 10 },
},
setup(props) {
const listData = ref([]);
const page = ref(1);
const isLoading = ref(false);
const isRefreshing = ref(false);
const isLoadFailed = ref(false);
const isFinished = ref(false);
const isRequesting = ref(false); // 新增一个变量
// 请求数据
const requestData = async (pageNum = 1, isRefresh = false) => {
if (isRequesting.value) return; // 如果正在请求数据,则直接返回
isRequesting.value = true; // 设置正在请求数据的状态为 true
try {
const { data } = await axios.get(
`${props.api}?page=${pageNum}&pageSize=${props.pageSize}`
);
const list = data[props.dataKey];
if (isRefresh) {
listData.value = list;
} else {
listData.value = [...listData.value, ...list];
}
page.value = pageNum;
isFinished.value = listData.value.length >= data.total;
} catch (error) {
console.error(error);
showToast.fail("网络异常,请稍后重试");
isLoadFailed.value = true;
} finally {
isLoading.value = false;
isRefreshing.value = false;
isRequesting.value = false; // 在请求数据结束后,将正在请求数据的状态设置为 false
}
};
// 下拉刷新
const handleRefresh = () => {
isRefreshing.value = true;
requestData(1, true);
};
// 底部加载
const handleLoad = () => {
if (isLoading.value || isFinished.value) return;
isLoading.value = true;
requestData(page.value + 1);
};
// 页面初始化加载数据
requestData();
return {
listData,
isLoading,
isRefreshing,
isLoadFailed,
isFinished,
handleRefresh,
handleLoad,
};
},
};
</script>
父组件使用
<template>
<List v-bind="{ api, pageSize, dataKey: 'list' }">
<template #list-item="{ items }">
<div v-for="item in items" :key="item.id">
<div>{{ item.name }}</div>
<div>{{ item.description }}</div>
</div>
</template>
</List>
</template>
<script>
import List from "../components/List.vue";
import { defineComponent, ref } from "vue";
export default defineComponent({
components: { List },
setup() {
const api = "/api/data";
const pageSize = 10;
return {
api,
pageSize,
};
},
});
</script>