摘要
在前端开发中,一个基础的分页列表通常需要维护 page、pageSize、total、loading、error 等多个状态,并编写翻页、筛选防抖、增删改操作等样板代码,实现量约 50-60 行。alova 的 usePagination Hook 将分页场景的模式抽象为声明式配置,核心代码压缩至约 3 行,同时提供相邻页预加载、列表项乐观更新、多粒度操作状态等手写方案容易忽略的能力。本文通过代码对比,分析 usePagination 的核心用法、配置项含义及适用边界。
一、手写分页的实现
以下是一个标准的分页列表手写实现(Vue 3 Composition API):
javascript
// 状态声明 --- 6 个 ref
const list = ref([]);
const loading = ref(false);
const error = ref(null);
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const searchKeyword = ref('');
// 请求函数 --- 手动管理 loading/error 切换
const fetchList = async () => {
loading.value = true;
error.value = null;
try {
const res = await axios.get('/api/users', {
params: {
page: page.value,
pageSize: pageSize.value,
keyword: searchKeyword.value,
},
});
list.value = res.data.data;
total.value = res.data.total;
} catch (e) {
error.value = e.message;
} finally {
loading.value = false;
}
};
// 翻页 → 改 page → 手动 fetch
const changePage = (p) => {
page.value = p;
fetchList();
};
// 每页条数变化 → 重置 page → 手动 fetch
const changePageSize = (ps) => {
pageSize.value = ps;
page.value = 1;
fetchList();
};
// 搜索 → 重置 page → 手动 fetch
const onSearch = (keyword) => {
searchKeyword.value = keyword;
page.value = 1;
fetchList();
};
// 删除 → 调接口 → 手动 fetch
const deleteItem = async (id) => {
await axios.delete(`/api/users/${id}`);
fetchList();
};
onMounted(() => fetchList());
核心逻辑(GET /api/users)仅 1 行,其余 50+ 行都属于"请求基础设施"------状态声明、加载切换、翻页联动、筛选重置。这些代码在各个列表页面反复出现。
二、usePagination 的声明式方案
usePagination 将分页场景固化为 hook:
javascript
import { usePagination } from 'alova/client';
const searchKeyword = ref('');
const {
loading, data, error,
page, pageSize, total, pageCount, isLastPage,
fetching, removing, replacing, status,
refresh, insert, remove, replace, reload,
onSuccess, onError, onComplete,
} = usePagination(
(page, pageSize) => alovaInstance.Get('/api/users', {
params: { page, pageSize, keyword: searchKeyword.value },
}),
{
initialPage: 1,
initialPageSize: 10,
watchingStates: [searchKeyword],
debounce: 300,
}
);
配置完成后,以下能力由框架接管:
翻页与每页条数变更
修改 page 或 pageSize 自动发起请求,pageSize 变更时自动重置为第1页:
javascript
page.value = 3; // 跳转到第3页,自动请求
pageSize.value = 20; // 修改每页条数,自动重置第1页并请求
筛选条件防抖搜索
通过 watchingStates 监听筛选条件变化,结合 debounce 实现防抖:
javascript
searchKeyword.value = '张三'; // 300ms 防抖后自动从第1页请求
列表项乐观操作
insert、remove、replace 操作本地列表并同步服务端,无需手动 refresh:
javascript
await insert({ id: 99, name: '新用户' }, 0); // 在前面插入
await remove(2); // 删除第3项
await replace({ id: 5, name: '更新后' }, 4); // 替换第5项
await refresh(page.value); // 强制刷新当前页
await reload(); // 清空数据,重新加载第1页
相邻页预加载
默认开启上一页和下一页的预加载。用户翻页时数据已从缓存读取,无需等待网络请求。
操作级状态
提供比单一 loading 更细粒度的状态:
javascript
loading, // 当前页请求中
fetching, // 预加载请求中(不影响当前页 UI)
removing, // 正在删除的行索引数组
replacing, // 正在替换的行索引
status, // 当前操作:"loading" | "removing" | "inserting" | "replacing"
便于行级操作时显示独立 loading,而不阻塞整个列表。
三、技术原理
代码量减少的根本原因在于抽象层次的提升:
- 传统方案使用 Axios/fetch 处理单次 HTTP 请求,开发者需自行管理每次请求的全部状态
usePagination将"分页列表请求"视为一个完整的业务场景,将通用逻辑(loading 切换、错误捕获、分页参数维护、翻页联动)内置到 hook 中- 各操作函数(insert/remove/replace)在乐观更新本地列表后自动同步服务端,减少手动 refresh
四、适用场景与局限
适用场景:
- 标准 CRUD 管理页面:用户管理、订单管理、内容管理等需要分页、搜索、增删改的列表
- 多条件筛选 + 分页组合:筛选条件变化自动重置页码的场景
- 列表项频繁操作:行内编辑、删除、插入频繁,可利用乐观更新避免每次操作后重新请求
- API 返回标准分页结构:
{ data: [], total: number }或可配置的结构
不适用场景:
- 游标分页(Cursor-based):使用
after/before游标的 API,页码模型无法直接映射 - 双端无限滚动:同时支持上下加载更多(如聊天记录),usePagination 追加模式仅支持单向
- 多列表联动:一个操作需同步更新多个分页列表且更新顺序有严格要求
- 多接口数据聚合:列表数据需从多个接口拼装且无法通过
data/total回调完成
其他考量:
- 学习成本:需理解策略 hook 的概念和各配置项行为
- 调试透明度:封装层可能增加底层请求行为的排查复杂度
- 可定制性:受 hook 预设行为约束,极端定制场景需自行实现
五、总结
usePagination 本质上不是在替换 HTTP 库,而是将分页场景的通用模式固化为配置项,减少重复编码的同时引入预加载、操作级状态等手写方案容易忽略的能力。对于标准分页场景,其代码量和维护成本的减少是显著的;对于超出设计边界的场景,传统手写方案仍然更加灵活。
标签: alova, usePagination, 前端分页, Vue, React
作者简介: 爱编程的小金,前端工程师,关注前端工程化与请求架构优化。