分页加载、无限列表加载:一个usePagination全搞定

612 阅读4分钟

上一篇文章中,我们介绍了alova从0到1的基本使用,今天我们继续使用alova来完成列表的加载与操作,希望能对你有所帮助。

1.分页加载

分页加载是一个非常常见的需求,特别是在一些后台管理系统中,表格+分页非常的常见。要完成这个需求,通常除了请求接口获取数据以外,我们还需要维护loading状态、处理分页数据、还需要支持关键词搜索筛选、防抖操作等,而这些,我们仅需要通过一个usePagination的hook就能完成。

import { getListWithTotal } from '@/API'
import { usePagination } from '@alova/scene-vue';


const { data: list, total, loading, page, pageSize } = usePagination((page, pageSize) => getListWithTotal(page, pageSize), {
    append: false,  // 数据不追加
    data: res => res.list,  // 定义如何取data数据
    total: res => res.total,    // 定义如何取total数据
    initialPageSize: 20,    // 设置默认pageSize
})

我们注意到,usePagination不仅帮我们维护好了列表数据,还有loading状态,当前页数page以及每一页数量pageSize,通过这些参数我们就可以配合一些分页组件,很方便的完成数据的分页加载。

要特别注意的是,对于pagepageSize,当其变化时,usePagination会自动发起接口请求,重新获取数据。

<n-data-table :columns="columns" striped :data="list" :max-height="350" :loading="loading" :pagination="false"/>
<n-pagination v-model:page="page" v-model:pageSize="pageSize" :item-count="total" :page-sizes="[10, 20, 30, 40, 50]" show-size-picker></n-pagination>

image.png

2.无限列表

无限列表是移动端场景的一个需求,用户滚到列表,快达到底部时,自动触发请求下一个的数据,其实其本质上也是一个分页加载。

除了需要维护loading状态以外,无限列表还需要额外对是否还有更多数据进行管理。

2.1 接口返回total

这种情况下,后台接口除了需要返回数据列表以外,还需要额外返回数据总量total,我们就可以根据total与当前前端的数据量进行对比,来判断是否还有更多数据。

/**
* 无限滚动,接口有total
*/

import { getListWithTotal } from '@/API'
import { usePagination } from '@alova/scene-vue';
import { throttle } from 'lodash-es'

// 后端接口有返回total数量的时候,isLastPage会按照total和当前list中的数量,大于等于时,则认为没有更多数据
const { data: list, loading, page, isLastPage, reload } = usePagination((page, pageSize) => getListWithTotal(page, pageSize), {
    append: true,  // 数据追加
    data: res => res.list,  // 定义如何取data数据
    total: res => res.total,    // 定义如何取total数据
    initialPageSize: 20,    // 设置默认pageSize
    preloadNextPage: false,
    preloadPreviousPage: false
})

const handleLoad = throttle(() => {
    if (isLastPage.value) return
    page.value++
}, 500) 

注意这里,我们需要告诉usePagination如何去获取list数据,以及如何获取total的数量。 这样的话,我们就可以很方便的获取isLastPage的状态,而不需要去额外维护。

<section class="w-750px my-0 mx-auto">
        <n-button @click="reload">刷新</n-button>
        <n-infinite-scroll class="h-500px" :distance="50" @load="handleLoad">
            <section v-for="p in list" :key="p.id" class="flex flex-row py-3 mb-4 border-b-(dashed gray)">
                <img :src="p.photoUrls[0]" class="w-20 h-20 rounded-full mr-5" alt="">
                <section class="flex flex-col h-20">
                    <h2 class="text-sm">{{ p.id }} - {{ p.name }}</h2>
                    <n-space>
                        <n-tag v-for="t in p.tags" :key="t.id" type="success" size="small">{{ t.name }}</n-tag>
                    </n-space>
                </section>
            </section>
            <div v-if="loading" class="text-center">
                加载中...
            </div>
            <div v-else-if="isLastPage" class="text-center">
                没有更多了 🤪
            </div>
     </n-infinite-scroll>
</section>

image.png

有了list,loading,isLastPage,就可以很方便配合组件进行使用,快速完成一个加载更多列表的开发。

2.2 接口未返回total

后端没有返回total数量时,isLastPage 按照当前请求的返回的数据长度是否小于pageSize,如果pageSize=20,那么如果只返回10条数据,则默认认为没有更多数据了。

/**
* 无限滚动,接口有total
*/

import { getListNoTotal } from '@/API'
import { usePagination } from '@alova/scene-vue';
import { throttle } from 'lodash-es'

// 后端没有返回total数量时,isLastPage 按照当前请求的返回的数据长度是否小于pageSize,如果pageSize=20,那么如果只返回10条数据,则默认认为没有更多数据了
const { data: list, loading, page, isLastPage, reload } = usePagination((page) => getListNoTotal(page), {
    append: true,  // 数据追加
    data: res => res,  // 定义如何取data数据
    preloadNextPage: false,
    preloadPreviousPage: false
})

const handleLoad = throttle(() => {
    if (isLastPage.value) return
    page.value++
}, 500) 

3.列表数据的插入、修改、删除

列表除了数据展示以外,我们通常还需要对列表进行一些额外的操作。 usePagination暴露出来的几个方法insert, replace, remove等可以帮助我们完成操作。

import { getListWithTotal, addPet } from '@/API'
import { usePagination } from '@alova/scene-vue';
import { throttle } from 'lodash-es'

const { data: list, loading, page, isLastPage, reload, insert, replace, remove } = usePagination((page, pageSize) => getListWithTotal(page, pageSize), {
    append: true,  // 数据追加
    data: res => res.list,  // 定义如何取data数据
    total: res => res.total,    // 定义如何取total数据
    initialPageSize: 20,    // 设置默认pageSize
    preloadNextPage: false,
    preloadPreviousPage: false
})

const handleLoad = throttle(() => {
    if (isLastPage.value) return
    page.value++
}, 500)

<section class="w-750px my-0 mx-auto">
        <n-space>
            <n-button @click="reload">刷新</n-button>
            <n-button @click="mockAdd">插入数据</n-button>
        </n-space>
        <n-infinite-scroll class="h-500px" :distance="50" @load="handleLoad">
            <section v-for="(p, idx) in list" :key="p.id" class="flex flex-row py-3 mb-4 border-b-(dashed gray)">
                <img :src="p.photoUrls[0]" class="w-20 h-20 rounded-full" alt="">
                <section class="flex flex-col h-20 mx-5">
                    <h2 class="text-sm">{{ p.id }} - {{ p.name }}</h2>
                    <n-space>
                        <n-tag v-for="t in p.tags" :key="t.id" type="success" size="small">{{ t.name }}</n-tag>
                    </n-space>
                </section>
                <section class="flex flex-col justify-center  h-20">
                    <n-space>
                        <n-button @click="mockUpdate(idx)">修改</n-button>
                        <n-button @click="mockDel(idx)">删除</n-button>
                    </n-space>

                </section>
            </section>
            <div v-if="loading" class="text-center">
                加载中...
            </div>
            <div v-else-if="isLastPage" class="text-center">
                没有更多了 🤪
            </div>
        </n-infinite-scroll>
    </section>

3.1 插入数据

我们先请求接口(一般是发布接口),然后将数据插入到列表最前面。

// 模拟插入数据
const mockAdd = async () => {
    const pet = await addPet().send()
    insert(pet, 0)
}

image.png

3.2 修改数据

// 模拟修改
const mockUpdate = (idx: number) => {
    const newPet = list.value[idx]
    newPet.name = 'New Of ' + newPet.name
    replace(newPet, idx)
}

通过replace方法,我们可以直接替换当前列表中指定位置的数据。

3.3 删除数据

通过remove方法,我们可以直接删除当前列表中指定位置的数据。

// 模拟删除
const mockDel = (idx: number) => {
    remove(idx)
}

OK,以上就是usePagination的一些常用操作,希望对你有所帮助,减少你的代码量,提高你的代码质量。


另外本篇文章中所使用到的示例代码都已经上传到gitee仓库中,如有所需,欢迎自取。