分页列表是前端开发中复用度最高的场景。一个基础分页手写实现需要维护 page、pageSize、total、loading 等 6+ 个状态,以及翻页联动、筛选防抖、列表操作等样板代码,核心逻辑轻松到 50 行以上。alova 的 usePagination Hook 将这些通用模式抽象为配置项,核心代码压缩到约 3 行。本文以代码对比的方式分析其核心用法和适用边界。

以下是一个标准的分页列表手写实现(Vue 3 Composition API):
// 状态声明 — 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 将上述所有基础设施固化为 hook 内部逻辑:
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 自动发起请求,无需手动 fetch。pageSize 变更时自动重置 page 为 1:
// 跳转到第3页 — 自动请求
page.value = 3;// 修改每页条数 — 自动重置第1页并请求
pageSize.value = 20;
通过 watchingStates 监听响应式状态变化,结合 debounce 实现防抖,且筛选条件变化自动重置为第1页:
// 用户在搜索框输入,300ms 防抖后自动从第1页请求
searchKeyword.value = '张三';
insert、remove、replace 操作本地列表并同步服务端,无需手动 refresh:
// 在前面插入一项
await insert({ id: 99, name: '新用户' }, 0);// 删除第3项(索引从0开始)
await remove(2);// 替换第5项
await replace({ id: 5, name: '更新后的名称' }, 4);// 刷新当前页(忽略缓存,强制请求)
await refresh(page.value);// 清空数据重新加载第1页
await reload();
默认开启上一页和下一页的预加载(preloadPreviousPage: true,preloadNextPage: true)。当用户点击"下一页"时,数据已从缓存读取,无需等待网络请求。
usePagination 提供了比单一 loading 更细粒度的状态:
loading, // 当前页请求中
fetching, // 预加载请求中(不影响当前页 UI)
removing, // 正在删除的行索引数组 — 用于行级删除 loading
replacing, // 正在替换的行索引 — 用于行级操作 loading
status, // 当前操作类型:"loading" | "removing" | "inserting" | "replacing"
相比手写方案中 loading 只有一个布尔值,usePagination 区分了页面加载和预加载加载,同时提供了操作级别的状态,可以用于控制表中某一行删除按钮的独立 loading 态,而不阻塞整个列表。
usePagination 在以下场景中有明显收益:
insert/remove/replace 避免每次操作后重新请求{ data: [], total: number } 或可通过 data/total 回调适配的结构以下场景不建议直接使用 usePagination:
after/before 游标的 API,usePagination 的页码模型无法直接映射,需要自行实现data/total 回调完成映射的复杂场景| 维度 | 手写分页 | usePagination |
|---|---|---|
| 代码量 | 50-60 行 | 核心约 3 行配置 |
| 翻页逻辑 | 手动改 page + 调 fetch | 自动响应 page 变化 |
| 筛选防抖 | 自行实现 debounce + reset page | watchingStates + debounce |
| 列表操作 | 操作后手动 refresh | insert/remove/replace 乐观更新 |
| 预加载 | 需自行实现 | 内置,默认开启 |
| 状态粒度 | 单一 loading | 多级:loading/fetching/removing/replacing |
| 可定制性 | 完全可控 | 受 hook 预设约束 |
| 学习成本 | 纯 JS/框架基础 | 需理解配置项和 hook 行为 |
usePagination 的核心价值在于将分页场景的通用模式固化为配置项,减少重复编码的同时提供了手写方案中易被忽略的能力——预加载、操作级状态、搜索防抖。对于标准分页场景,代码量和维护成本的减少是显著的;对于超出其设计边界的场景,自定义实现仍然更合适。