不写组件,你说你能笑傲前端界吗?

88 阅读1分钟

Vue + Vant 移动端列表组件实现

前言

列表组件封装得越多,Bug就藏得越深,不如把组件拆成几个页面算了!教你高大上封装移动端的list组件

实现思路

  1. setup 函数中使用 refreactive 创建响应式变量,如 listData 存储列表数据,page 存储当前页码等。
  2. 在组件挂载时通过 requestData 函数请求第一页数据,将数据存入 listData 中。
  3. 监听下拉刷新和底部加载事件,在事件处理函数中调用 requestData 函数请求数据,根据请求结果更新相应的变量和数据。
  4. 在模板中使用 Vant 组件库中的 van-listvan-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>