跳到主要内容
版本:v3

分页请求策略

策略类型

use hook

在使用扩展 hooks 前,确保你已熟悉了 alova 的基本使用。

为分页场景下设计的 hook,它可以帮助你自动管理分页数据,数据预加载,减少不必要的数据刷新,流畅性提高 300%,编码难度降低 50%。你可以在下拉加载和页码翻页两种分页场景下使用它,此 hook 提供了丰富的特性,助你的应用打造性能更好,使用更便捷的分页功能。

特性

  • 丰富全面的分页状态;
  • 丰富全面的分页事件;
  • 更改 page、pageSize 自动获取指定分页数据;
  • 数据缓存,无需重复请求相同参数的列表数据;
  • 前后页预加载,翻页不再等待;
  • 搜索条件监听自动重新获取页数;
  • 支持列表数据的新增、编辑、删除;
  • 支持刷新指定页的数据,无需重置;
  • 请求级搜索防抖,无需自行维护;

使用

渲染列表数据

<template>
<div
v-for="item in data"
:key="item.id">
<span>{{ item.name }}</span>
</div>
<button @click="handlePrevPage">上一页</button>
<button @click="handleNextPage">下一页</button>
<button @click="handleSetPageSize">设置每页数量</button>
<span>共有{{ pageCount }}页</span>
<span>共有{{ total }}条数据</span>
</template>

<script setup>
import { queryStudents } from './api.js';
import { usePagination } from 'alova/client';

const {
// 加载状态
loading,

// 列表数据
data,

// 是否为最后一页
// 下拉加载时可通过此参数判断是否还需要加载
isLastPage,

// 当前页码,改变此页码将自动触发请求
page,

// 每页数据条数
pageSize,

// 分页页数
pageCount,

// 总数据量
total
} = usePagination(
// Method实例获取函数,它将接收page和pageSize,并返回一个Method实例
(page, pageSize) => queryStudents(page, pageSize),
{
// 请求前的初始数据(接口返回的数据格式)
initialData: {
total: 0,
data: []
},
initialPage: 1, // 初始页码,默认为1
initialPageSize: 10 // 初始每页数据条数,默认为10
}
);

// 翻到上一页,page值更改后将自动发送请求
const handlePrevPage = () => {
page.value--;
};

// 翻到下一页,page值更改后将自动发送请求
const handleNextPage = () => {
page.value++;
};

// 更改每页数量,pageSize值更改后将自动发送请求
const handleSetPageSize = () => {
pageSize.value = 20;
};
</script>

指定分页数据

每个分页数据接口返回的数据结构各不相同,因此我们需要分别告诉usePagination列表数据与总条数,从而帮助我们管理分页数据。

假如你的分页接口返回的数据格式是这样的:

interface PaginationData {
totalNumber: number;
list: any[];
}

此时你需要通过函数的形式返回列表数据与总条数。

usePagination((page, pageSize) => queryStudents(page, pageSize), {
// ...
total: response => response.totalNumber,
data: response => response.list
});

如果不指定 total 和 data 回调函数,它们将默认通过以下方式获取数据。

const total = response => response.total;
const data = response => response.data;
注意

data 回调函数必须返回一个列表数据,表示分页中所使用的数据集合,而 total 主要用于计算当前页数,在 total 回调函数中如果未返回数字,将会通过请求的列表数量是否少于 pageSize 值来判断当前是否为最后一页,这一般用于下拉加载时使用。

开启追加模式

默认情况下,翻页时会替换原有的列表数据,而追加模式是在翻页时会将下一页的数据追加到当前列表底部,常见的使用场景是下拉加载更多。

usePagination((page, pageSize) => queryStudents(page, pageSize), {
// ...
append: true
});

预加载相邻页数据

为了让分页提供更好的体验,在当前页的上一页和下一页满足条件时将会自动预加载,这样在用户翻页时可直接显示数据而不需要等待,这是默认的行为。如果你不希望预加载相邻页的数据,可通过以下方式关闭。

usePagination((page, pageSize) => queryStudents(page, pageSize), {
// ...
preloadPreviousPage: false, // 关闭预加载上一页数据
preloadNextPage: false // 关闭预加载下一页数据
});
预加载触发条件

在开启预加载时,并不会一味地加载下一页,需要满足以下两个条件:

  1. 预加载是基于缓存的,用于分页加载的 Method 实例必须开启缓存,默认情况下 get 请求会有 5 分钟的 memory 缓存,如果是非 get 请求或者全局关闭了缓存,你还需要在这个 Method 实例中单独设置cacheFor开启缓存。
  2. 根据totalpageSize参数判断出下一页还有数据。

除了onSuccess、onError、onComplete请求事件外,在触发了预加载时,你还可以通过fetching来获知预加载状态,还可以通过onFetchSuccess、onFetchError、onFetchComplete来监听预加载请求的事件。

const {
// 预加载状态
fetching,

// 预加载成功事件绑定函数
onFetchSuccess,

// 预加载错误事件绑定函数
onFetchError,

// 预加载完成事件绑定函数
onFetchComplete
} = usePagination((page, pageSize) => queryStudents(page, pageSize), {
// ...
});

监听筛选条件

很多时候列表需要通过条件进行筛选,此时可以通过usePagination的状态监听来触发重新请求,这与 alova 提供的useWatcher是一样的。

例如通过学生姓名、学生年级进行筛选。

<template>
<input v-model="studentName" />
<select v-model="clsName">
<option value="1">Class 1</option>
<option value="2">Class 2</option>
<option value="3">Class 3</option>
</select>
<!-- ... -->
</template>

<script setup>
import { ref } from 'vue';
import { queryStudents } from './api.js';
import { usePagination } from 'alova/client';

// 搜索条件状态
const studentName = ref('');
const clsName = ref('');
const {
// ...
} = usePagination(
(page, pageSize) => queryStudents(page, pageSize, studentName.value, clsName.value),
{
// ...
watchingStates: [studentName, clsName]
}
);
</script>

useWatcher相同,你也可以通过指定debounce来实现请求防抖,具体可参考useWatcher 的 debounce 参数设置

usePagination((page, pageSize) => queryStudents(page, pageSize, studentName, clsName), {
// ...
debounce: 300 // 防抖参数,单位为毫秒数,也可以设置为数组对watchingStates单独设置防抖时间
});

需要注意的是,debounce是通过 useWatcher 中的请求防抖实现的。监听状态末尾分别还有 page 和 pageSize 两个隐藏的监听状态,也可以通过 debounce 来设置。

举例来说,当watchingStates设置了[studentName, clsName],内部将会监听[studentName, clsName, page, pageSize],因此如果需要对 page 和 pageSize 设置防抖时,可以指定为[0, 0, 500, 500]

关闭初始化请求

默认情况下,usePagination会在初始化时发起请求,但你也可以使用immediate关闭它,并在后续通过send函数,或者改变pagepageSize,以及watchingStates等监听状态来发起请求。

usePagination((page, pageSize) => queryStudents(page, pageSize, studentName, clsName), {
// ...
immediate: false
});

列表操作函数

usePagination 提供了功能完善的列表操作函数,它可以在不重新请求列表的情况下,做到与重新请求列表一致的效果,大大提高了页面的交互体验,具体的函数说明继续往下看吧!

插入列表项

你可以用它插入列表项到列表任意位置,它将会在插入之后去掉末尾的一项,来保证和重新请求当前页数据一致的效果。

/**
* 插入一条数据
* 如果未传入index,将默认插入到最前面
* 如果传入一个列表项,将插入到这个列表项的后面,如果列表项未在列表数据中将会抛出错误
* @param item 插入项
* @param indexOrItem 插入位置(索引)
*/
declare function insert(item: LD[number], indexOrItem?: number | LD[number]): void;

以下为非 append 模式下(页码翻页场景),返回第一页再插入列表项的示例:

page.value = 1;
nextTick(() => {
insert(newItem, 0);
});

以下为在append 模式下(下拉加载场景),插入列表项后滚动到最顶部的示例:

insert(newItem, 0);
nextTick(() => {
window.scrollTo(0, {});
});

你也可以将insert的第二个参数指定为列表项,当查找到这个列表项的相同引用时,插入项将插入到这个列表项的后面。

insert(newItem, afterItem);
注意

为了让数据正确,insert 函数调用会清除全部缓存。

移除列表项

在下一页有缓存的情况下,它将会在移除一项后使用下一页的缓存补充到列表项尾部,来保证和重新请求当前页数据一致的效果,在append 模式非 append 模式下表现相同。

/**
* 移除一条数据
* 如果传入的是列表项,将移除此列表项,如果列表项未在列表数据中将会抛出错误
* @param position 移除的索引或列表项
*/
declare function remove(position: number | LD[number]): void;

你也可以将remove的第二个参数指定为列表项,当查找到这个列表项的相同引用时,将会移除此列表项。

但在以下两种情况下,它将重新发起请求刷新对应页的数据:

  1. 下一页没有缓存
  2. 同步连续调用了超过下一页缓存列表项的数据,缓存数据已经不够补充到当前页列表了。
注意

为了让数据正确,remove 函数调用会清除全部缓存。

更新数据项

当你想要更新列表项时,使用此函数实现。

/**
* 替换一条数据
* 当position传入数字时表示替换索引,负数表示从末尾算起,当 position 传入的是列表项,将替换此列表项,如果列表项未在列表数据中将会抛出错误
* @param item 替换项
* @param position 替换位置(索引)或列表项
*/
declare function replace(
item: LD extends any[] ? LD[number] : any,
position: number | LD[number]
): void;

你也可以将replace的第二个参数指定为列表项,当查找到这个列表项的相同引用时,将会替换此列表项。

刷新指定页的数据

当你在数据操作后不希望本地更新列表项,而是重新请求服务端的数据,你可以用 refresh 刷新任意页的数据,而不需要重置列表数据让用户又从第一页开始浏览。

/**
* 刷新指定页码数据,此函数将忽略缓存强制发送请求
* 如果未传入页码则会刷新当前页
* 如果传入一个列表项,将会刷新此列表项所在页
* @param pageOrItemPage 刷新的页码或列表项
* @returns [v3.1.0+]包含响应数据的promise
*/
declare function refresh(pageOrItemPage?: number | LD[number]): Promise<AG['Responded']>;

在 append 模式下,你可以将refresh的参数指定为列表项,当查找到这个列表项的相同引用时,刷新此列表项所在页数的数据。

手动更新列表数据

使用update函数更新响应式数据,这与useRequest 的 update相似,唯一不同的是,在调用update更新data时,更新的是列表数据,而非响应数据。这在手动清除列表数据,而不重新发起请求时很有用。

// 情况列表数据
update({
data: []
});

重置列表

它将清空全部缓存,并重新加载第一页。

/**
* 从第一页开始重新加载列表,并清空缓存
* @returns [v3.1.0+]promise实例,表示是否重置成功
*/
declare function reload(): Promise<void>;

updateState兼容使用

你还可以使用updateState更新usePagination导出的响应式数据。

updateState(listMethod, {
// 更新导出的list数据,即data。
data: oldList => [...oldList, ...newList],
// 更新导出的total数据
total: oldTotal => oldTotal + newList.length,
// 更新导出的page数据
page: oldPage => oldPage + 1,
// 更新导出的pageSize数据
pageSize: oldPageSize => oldPageSize + 10
});

点此查看关于updateState的详细用法。

由于usePaginationdata类型不是直接由 method 推断而来,因此在updateState中需要手动指定类型。

updateState<Item[]>(listMethod, {
data: oldList => [...oldList, ...newList]
});

API

Hook 配置

继承useWatcher所有配置。

名称描述类型默认值版本
initialPage初始页码number1-
initialPageSize初始每页数据条数number10-
watchingStates状态监听触发请求,使用 useWatcher 实现any[][page, pageSize]-
debounce状态监听的防抖参数,使用 useWatcher 实现number | number[]--
append是否开启追加模式booleanfalse-
data指定分页的数组数据(response: any) => any[]response => response.data-
total指定数据总数量值(response: any) => numberresponse => response.total-
preloadPreviousPage是否预加载上一页数据booleantrue-
preloadNextPage是否预加载下一页数据booleantrue-

响应式数据

继承useWatcher所有响应式数据。

名称描述类型版本
page当前页码,由 initialPage 决定number-
pageSize当前每页数量,由 initialPageSize 决定number-
data分页列表数组数据,由 data 配置得到any[]-
total数据总数量,由 total 配置得到,可为空number-
pageCount总页数,由 total 和 pageSize 计算得到number-
isLastPage当前是否为最后一页,pageCount 有值时会通过 pageCount 和 page 对比得到,否则会通过列表数据长度是否少于 pagSize 得到number-
fetching是否正在预加载数据boolean-

操作函数

继承useWatcher所有操作函数。

名称描述函数参数返回值版本
refresh刷新指定页码数据,此函数将忽略缓存强制发送请求,append 模式下可传入列表项表示刷新此列表项所在的页数pageOrItemPage: 刷新的页码或列表项Promise<AG['Responded']>v3.1.0+
insert插入一条数据,如果未传入 index,将默认插入到最前面,如果传入一个列表项,将插入到这个列表项的后面,如果列表项未在列表数据中将会抛出错误1. item: 插入项
2. indexOrItem: 插入位置(索引)或列表项,默认为 0
--
remove移除一条数据,当传入数字时表示移除的索引,当 position 传入的是列表项,将移除此列表项,如果列表项未在列表数据中将会抛出错误position: 移除位置(索引)或列表项--
replace替换一条数据,当第二个参数传入数字时表示替换索引,负数表示从末尾算起,当 position 传入的是列表项,将替换此列表项,如果列表项未在列表数据中将会抛出错误1. item: 替换项
2. position: 替换位置(索引)或列表项,传入负数时表示从末尾开始算起
--
reload清空数据,并重新请求第一页数据-Promise<void>v3.1.0+
update更新状态数据,与 alova 的 use hook 用法相同,但在更新 data 字段时是更新列表数据newFrontStates:新的状态数据对象--

事件

继承useWatcher所有事件。

名称描述回调参数版本
onFetchSuccessfetch 成功的回调绑定函数event: alova 成功事件对象-
onFetchErrorfetch 失败的回调绑定函数event: alova 失败事件对象-
onFetchCompletefetch 完成的回调绑定函数event: alova 完成事件对象-