分页列表是前端开发中复用度最高的场景。一个基础分页手写实现需要维护 page、pageSize、total、loading 等 6+ 个状态,以及翻页联动、筛选防抖、列表操作等样板代码,核心逻辑轻松到 50 行以上。alova 的 usePagination Hook 将这些通用模式抽象为配置项,核心代码压缩到约 3 行。本文以代码对比的方式分析其核心用法和适用边界。
手写分页:一份必然重复的作业
以下是一个标准的分页列表手写实现(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 自动发起请求,无需手动 fetch。pageSize 变更时自动重置 page 为 1:
javascript
// 跳转到第3页 --- 自动请求
page.value = 3;
// 修改每页条数 --- 自动重置第1页并请求
pageSize.value = 20;
筛选条件防抖搜索
通过 watchingStates 监听响应式状态变化,结合 debounce 实现防抖,且筛选条件变化自动重置为第1页:
javascript
// 用户在搜索框输入,300ms 防抖后自动从第1页请求
searchKeyword.value = '张三';
列表项乐观操作
insert、remove、replace 操作本地列表并同步服务端,无需手动 refresh:
javascript
// 在前面插入一项
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)。当用户点击"下一页"时,数据已从缓存读取,无需等待网络请求。
预加载的前提:请求方法已启用缓存(GET 默认有 5 分钟内存缓存),且根据
total和pageSize判断下一页有数据。
操作级状态
usePagination 提供了比单一 loading 更细粒度的状态:
javascript
loading, // 当前页请求中
fetching, // 预加载请求中(不影响当前页 UI)
removing, // 正在删除的行索引数组 --- 用于行级删除 loading
replacing, // 正在替换的行索引 --- 用于行级操作 loading
status, // 当前操作类型:"loading" | "removing" | "inserting" | "replacing"
相比手写方案中 loading 只有一个布尔值,usePagination 区分了页面加载和预加载加载,同时提供了操作级别的状态,可以用于控制表中某一行删除按钮的独立 loading 态,而不阻塞整个列表。
适用场景
usePagination 在以下场景中有明显收益:
- 标准 CRUD 管理页面:用户管理、订单管理、内容管理等需要分页、搜索、增删改的列表页面
- 多条件筛选 + 分页组合:多个筛选条件联动,每次条件变化自动重置页码的场景
- 列表项频繁操作 :行内编辑、删除、插入等操作频繁,可利用
insert/remove/replace避免每次操作后重新请求 - API 返回标准分页结构 :服务端返回
{ data: [], total: number }或可通过data/total回调适配的结构
不适用场景
以下场景不建议直接使用 usePagination:
- 游标分页(Cursor-based Pagination) :如"加载更多"使用
after/before游标的 API,usePagination 的页码模型无法直接映射,需要自行实现 - 双端无限滚动:同时支持向上和向下加载更多(如聊天记录),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 的核心价值在于将分页场景的通用模式固化为配置项,减少重复编码的同时提供了手写方案中易被忽略的能力------预加载、操作级状态、搜索防抖。对于标准分页场景,代码量和维护成本的减少是显著的;对于超出其设计边界的场景,自定义实现仍然更合适。