告别手写分页逻辑:usePagination 从 50 行到 3 行

分页列表是前端开发中复用度最高的场景。一个基础分页手写实现需要维护 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,
  }
);

配置完成后,以下能力由框架自动接管:

翻页与每页条数变更

修改 pagepageSize 自动发起请求,无需手动 fetch。pageSize 变更时自动重置 page 为 1:

javascript 复制代码
// 跳转到第3页 --- 自动请求
page.value = 3;

// 修改每页条数 --- 自动重置第1页并请求
pageSize.value = 20;

筛选条件防抖搜索

通过 watchingStates 监听响应式状态变化,结合 debounce 实现防抖,且筛选条件变化自动重置为第1页:

javascript 复制代码
// 用户在搜索框输入,300ms 防抖后自动从第1页请求
searchKeyword.value = '张三';

列表项乐观操作

insertremovereplace 操作本地列表并同步服务端,无需手动 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: truepreloadNextPage: true)。当用户点击"下一页"时,数据已从缓存读取,无需等待网络请求。

预加载的前提:请求方法已启用缓存(GET 默认有 5 分钟内存缓存),且根据 totalpageSize 判断下一页有数据。

操作级状态

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 的核心价值在于将分页场景的通用模式固化为配置项,减少重复编码的同时提供了手写方案中易被忽略的能力------预加载、操作级状态、搜索防抖。对于标准分页场景,代码量和维护成本的减少是显著的;对于超出其设计边界的场景,自定义实现仍然更合适。

相关推荐
小村儿4 小时前
连载12- Cluade code 的MCP 到底还用不用
前端·后端·ai编程
IT_陈寒4 小时前
Vite静态资源引用差点把我逼疯,原来要这样处理
前端·人工智能·后端
北城以北88884 小时前
Quartz定时任务
java·spring boot·intellij-idea
子兮曰4 小时前
WSL 配 GPU 用 Docker 的折腾指南(2026 年版)
linux·前端·后端
许彰午4 小时前
JWT的四种设计策略——轻量负载缓存外置上下文线程统一验证
java·安全·缓存·tomcat
lantian4 小时前
TypeScript 三斜线指令完全指南:从入门到理解为什么不再需要它
前端·javascript·vue.js
鹏多多4 小时前
锐评CSDN最近上线的AI数字营销:烂完之前最后再捞一笔
前端·后端·程序员
先吃饱再说4 小时前
从 WeUI 按钮组件学 BEM 命名规范:让 CSS 不再难维护
前端·代码规范
槑有老呆4 小时前
从前端 HTTP 请求到 LLM 接口调用:一篇文章带你彻底搞懂
前端