Pagination分页组件的实现细节与性能优化实践

司马燕燕 组件 阅读 2,694
赞 34 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

分页这个功能,我写了不下几十遍了。说实话,一开始我也踩过不少坑,但经过几个项目的折腾,总结出一套自己觉得还不错的写法。核心代码其实不复杂,重点是思路要清晰。

Pagination分页组件的实现细节与性能优化实践

下面是我常用的Pagination组件代码:

function Pagination({ total, pageSize, currentPage, onPageChange }) {
  const totalPages = Math.ceil(total / pageSize);
  const range = 2; // 当前页前后显示的页码数

  if (totalPages <= 1) return null;

  const pages = [];
  for (let i = Math.max(1, currentPage - range); i <= Math.min(totalPages, currentPage + range); i++) {
    pages.push(i);
  }

  return (
    <ul className="pagination">
      {currentPage > 1 && (
        <li onClick={() => onPageChange(currentPage - 1)}>上一页</li>
      )}

      {pages[0] > 1 && <li onClick={() => onPageChange(1)}>1</li>}
      {pages[0] > 2 && <li>...</li>}

      {pages.map(page => (
        <li 
          key={page} 
          className={page === currentPage ? 'active' : ''} 
          onClick={() => onPageChange(page)}
        >
          {page}
        </li>
      ))}

      {pages[pages.length - 1] < totalPages - 1 && <li>...</li>}
      {pages[pages.length - 1] < totalPages && (
        <li onClick={() => onPageChange(totalPages)}>{totalPages}</li>
      )}

      {currentPage < totalPages && (
        <li onClick={() => onPageChange(currentPage + 1)}>下一页</li>
      )}
    </ul>
  );
}

为什么这样写?首先,我把总条目数、每页显示数量、当前页和页码变更回调都作为props传入,这样组件的复用性就很强。其次,我用了range来控制显示的页码范围,这个值可以根据需求调整。

这几种错误写法,别再踩坑了

说真的,我在项目里见过太多分页的奇葩写法,有些甚至让我怀疑人生。最常见的错误有这么几种:

  • 直接在每个页面都写死分页逻辑,导致到处都是重复代码
  • 把所有页码都渲染出来,结果数据量大的时候直接卡死
  • 没有处理边界情况,比如第一页的时候还能点”上一页”,最后一页还能点”下一页”

举个例子,之前接手一个项目,看到这样的代码:

// 反面教材,请勿模仿
for (let i = 1; i <= totalPages; i++) {
  pageButtons += &lt;button&gt;${i}&lt;/button&gt;;
}

这代码看着简单,但当totalPages很大的时候,性能直接爆炸。而且完全没有考虑用户体验,页面上可能密密麻麻几百个按钮。

实际项目中的坑

在真实项目里,分页往往会遇到各种意想不到的问题。这里分享几个我踩过的坑:

第一个坑是API设计问题。有些后端接口要求传offset和limit,有些要求传pageNumber和pageSize,还有些直接要传起始ID和结束ID。我就遇到过一个特别离谱的情况:

// 后端要求的奇葩参数格式
fetch(https://jztheme.com/api/data?from=${(currentPage-1)*pageSize}&amp;size=${pageSize})

这种写法不仅容易算错,而且可读性极差。后来我和后端商量,统一改成pageNumber和pageSize,代码立马清爽很多。

第二个坑是数据更新不同步。有时候用户快速点击分页按钮,可能会导致请求交错返回,出现数据错乱的情况。我的解决方法是加了个loading状态:

const [loading, setLoading] = useState(false);

const handlePageChange = async (page) => {
  if (loading) return;
  setLoading(true);
  await fetchData(page);
  setLoading(false);
};

第三个坑是样式适配问题。尤其是移动端,如果页码太多会撑爆屏幕。所以我一般会给页码容器设置最大宽度,并加上overflow: auto。

一些小技巧分享

除了上面提到的核心逻辑,我还总结了一些实用的小技巧:

  • 给当前页加个明显的样式标识,比如加粗或者换个颜色
  • 当总页数超过一定数量时,自动隐藏部分页码,只保留首尾和当前页附近的页码
  • 记得处理键盘事件,让用户可以通过方向键切换分页
  • 添加防抖处理,防止用户疯狂点击分页按钮

这里有个小细节,我发现很多人忽略:当用户从搜索结果跳转到详情页,再返回列表页时,应该记住之前的分页状态。这个可以通过简单的状态管理或者URL参数来实现。

以上是我总结的最佳实践

分页看似简单,但要做好还是挺考验功底的。从我的经验来看,关键是要考虑全面:性能、用户体验、边界情况、特殊场景都要照顾到。

当然,我这套方案也不是完美无缺的,比如在超大数据量的情况下,可能需要更复杂的虚拟滚动方案。不过对于大多数业务场景来说,已经够用了。

如果你有更好的实现方式,或者遇到了什么奇葩的分页需求,欢迎在评论区交流。毕竟前端开发就是这样,总能遇到新花样,大家一起进步嘛。

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

暂无评论