手把手实现高性能React分页组件的实战经验分享

シ红爱 组件 阅读 1,704
赞 11 收藏
二维码
手机扫码查看
反馈

谁更灵活?谁更省事?

分页组件这玩意儿,看起来简单,写起来真能让你半夜改完第三版还发现“上一页”在第一页时居然没禁用。我做过不下二十个带分页的项目,从纯静态表格到实时搜索+无限滚动混合场景,踩过的坑足够堆成个小山包。这次不讲理论,就聊三个我实际用过、线上跑过半年以上的方案:手搓原生(React + useState)、Ant Design 的 Pagination、以及一个轻量但有点野路子的自定义 Hook 方案(usePagination)。不吹不黑,哪个好用、哪个反人类,咱一条条说。

手把手实现高性能React分页组件的实战经验分享

我一般先手搓,但只限小项目

小需求、内部系统、或者只是临时 demo,我第一反应就是自己写——因为快,可控,不引包。核心逻辑就三件事:当前页、总条数、每页几条,然后算出总页数、前后页范围、是否禁用按钮。代码也就十几行:

function SimplePagination({ total, pageSize = 10, current, onChange }) {
  const totalPages = Math.ceil(total / pageSize);
  const prevDisabled = current <= 1;
  const nextDisabled = current >= totalPages;

  return (
    <div className="flex items-center gap-2">
      <button
        onClick={() => !prevDisabled && onChange(current - 1)}
        disabled={prevDisabled}
        className="px-3 py-1 rounded border disabled:opacity-50"
      >
        上一页
      </button>
      <span className="text-sm">
        第 {current} 页,共 {totalPages} 页
      </span>
      <button
        onClick={() => !nextDisabled && onChange(current + 1)}
        disabled={nextDisabled}
        className="px-3 py-1 rounded border disabled:opacity-50"
      >
        下一页
      </button>
    </div>
  );
}

优点?一清二楚:没有 magic,状态全在眼皮底下,debug 不用翻源码;样式随便拧,Tailwind 一行搞定;也不怕哪天 Ant Design 升级把 showQuickJumper 的行为偷偷改了。缺点也很实在:没有页码跳转输入框、没有“…”省略逻辑、没有响应式断点适配(手机上一堆数字挤成一团)。我曾经在一个后台导出页里用了这个,结果运营同事非说“不能直接输页码太反人类”,最后还是换掉了——不是它不行,是业务方不认。

Ant Design 是我的“保底选项”

中大型项目,尤其团队协作、要长期维护的,我现在基本默认选 Ant Design 的 Pagination。不是因为它多牛,而是它够稳、文档够全、社区问题一搜一大把,连“如何让页码居中”这种弱智问题都有 StackOverflow 高赞答案。

import { Pagination } from 'antd';

<Pagination
  current={current}
  total={total}
  pageSize={10}
  onChange={onChange}
  showQuickJumper
  showSizeChanger
  onShowSizeChange={onPageSizeChange}
/>

它解决了我所有“不想自己搞”的事:页码自动折叠(比如 1 2 3 … 18 19 20)、键盘支持(Tab 切换+回车确认)、无障碍属性(aria-label 全都配好了)、还有那个救命的 showQuickJumper —— 我再也不用自己写 input + blur 校验逻辑了。但这里得提个真实坑:如果你用的是 useEffect 去同步后端返回的 current,而用户快速点两下“下一页”,current 状态可能还没更新完,导致组件卡在错误页码。我踩过三次,解决办法很简单:加个防抖或强制重置 key,比如 key={current}

那个野路子 Hook:usePagination,我私藏了半年

去年做一套数据治理平台,后端接口返回的分页字段是 pagepage_size,但 UI 要求显示“共 127 条,第 5/13 页”,还要支持 URL 同步(?page=5&size=10)。Ant Design 没法直接塞进 URL,手搓又太碎。我就写了这么个 Hook:

function usePagination(initial = { page: 1, size: 10 }) {
  const [pagination, setPagination] = useState(initial);

  const update = (updates) => {
    const newPage = { ...pagination, ...updates };
    setPagination(newPage);
    // 这里顺便 pushState,省得每个组件都处理
    const url = new URL(window.location);
    url.searchParams.set('page', newPage.page);
    url.searchParams.set('size', newPage.size);
    window.history.replaceState(null, '', url);
  };

  return {
    ...pagination,
    onChange: (page) => update({ page }),
    onPageSizeChange: (size) => update({ page: 1, size }),
  };
}

// 使用
const { page, size, onChange, onPageSizeChange } = usePagination();
// 然后传给任意分页 UI 组件(甚至可以是上面那个手搓的)

它不绑定 UI,不强制你用某套设计语言,就干一件事:管理分页参数 + 同步 URL。我把它和一个极简的 Tailwind 分页组件配着用,代码量比 Ant Design 少一半,但关键路径(URL 同步、参数校验、默认值 fallback)全在 Hook 里兜住了。唯一缺点:没现成的“跳转到末页”按钮,得自己加,但我加了一行:onClick={() => update({ page: Math.ceil(total / size) })},完事。

性能?其实真没差多少

有人问过我“三个方案哪个渲染更快”。实测过:10w 条数据分页,切换页码时 React DevTools 里看 render 时间,差异都在 0.5ms 以内。真正卡的永远是你的 fetch 请求和列表渲染(尤其是没做虚拟滚动的时候)。别在这儿抠毫秒,先把 useCallback 包好 onChange,再检查一下有没有在分页组件里重复请求数据就行。我见过最离谱的是一次分页点击触发了 4 次相同 API,因为父组件没 memo,子组件重渲染了四遍……

我的选型逻辑

总结一下我的个人规则:

  • 内部工具、MVP 快速验证 → 手搓:10 分钟搞定,不纠结
  • ToB 后台、多人协作、需要长期维护 → Ant Design:省心,出问题有地方查
  • 要深度定制 URL、多端统一、或者压根不用 Ant Design 主题 → usePagination Hook + 极简 UI:自由度拉满,我目前 70% 新项目走这条路

没有银弹。上周我还被迫在某个老项目里用 Vue 2 + 自己封装的 pagination mixin,因为升级 Vue 3 成本太高……所以,别迷信方案,盯紧你的场景、团队熟悉度、和下周能不能准时上线。

踩坑提醒:这三点一定注意

最后甩三个我血泪换来的提醒:

  • 后端返回的 total 是 0 时,current 很可能还是 1 —— 页面会白屏或报错:一定要在 useEffect 里加 guard:if (total === 0) setCurrent(1)
  • 用户手动改 URL 里的 page 参数,比如输成 ?page=abc,别直接 parseInt 就完事:我上次用 parseInt('abc') 得到 NaN,然后传给后端,API 直接 500……现在一律 Math.max(1, Number(page) || 1)
  • 移动端 touch 事件干扰分页按钮点击:某些安卓 WebView 里,快速连点两次“下一页”,第二次可能被识别为 touchmove 导致 click 失效。解决方案就一行 CSS:button { touch-action: manipulation; }

以上是我踩坑后的总结,希望对你有帮助。这个 usePagination Hook 我稍后会开源到 GitHub(不带任何框架依赖),欢迎 star。有更优的实现方式,或者你用过其他特别顺手的分页方案,欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论