高性能分页组件需避免重render整个nav,用template+clonenode复用按钮、动态裁剪页码数组、pushstate/popstate双向同步url,并处理total=0时显式设为1页。

直接用 HTML 模板构建高性能分页组件,关键不是“模板语法有多高级”,而是避免每次翻页都重 render 整个 nav,同时把页码生成、状态同步、DOM 更新三件事拆开做,让浏览器只更新真正需要的部分。
为什么不能用 innerHTML 重写整个分页栏
常见做法是每次点击页码就 container.innerHTML = renderPagination(current, total) —— 这会导致所有 button 节点被销毁重建,焦点丢失、事件监听器全掉、屏幕阅读器中断,且在低端设备上明显卡顿。
- DOM 重建开销大,尤其当页码多(比如 200+ 页)时,
innerHTML拼接字符串 + 解析 HTML 的成本远高于 patch 差异 - 键盘用户按空格/回车触发当前页按钮时,若该按钮被重建,焦点会回到页面顶部
-
aria-current="page"状态无法平滑过渡,辅助技术感知不到“当前页变更”这个语义动作
用 template + cloneNode 做轻量级复用
把页码按钮结构预定义在 <template></template> 中,只 clone 已有的 button 模板节点,再改 textContent 和属性,不碰 HTML 字符串。
- 提前定义:
<template id="page-btn"><button type="button" data-page="1">1</button></template> - 生成页码时只 clone、设
data-page、改textContent、加aria-current="page"或disabled - 对“上一页/下一页”按钮也复用同一模板,仅通过
data-direction="prev"区分行为 - 首次渲染后,后续翻页只更新
textContent和 class/attribute,不 touchinnerHTML
页码数组必须动态裁剪,不能全量生成
getPageNumbers(currentPage, totalPages, maxVisible = 5) 返回的不是纯数字数组,而是混合类型:[1, '', 4, 5, 6, '', 100]。渲染时需判断每个项是数字还是 ''(代表省略号)。
-
''应渲染为<span aria-hidden="true">…</span>,不可聚焦、无交互,语义正确 - 首尾固定显示
1和totalPages,中间最多保留maxVisible - 2个连续页码(如 maxVisible=7,则中间最多 5 个) - 边界修正逻辑必须存在:当
currentPage≤ 3 时,右侧要补满;当 ≥totalPages - 2时,左侧要拉齐——否则会出现1 … 3 4 5却没显示totalPages的情况
URL 同步必须用 pushState + popstate 双向绑定
只写 history.pushState 不够,用户手动改地址栏或点浏览器前进/后退时,页面状态不会自动更新,导致 UI 和 URL 不一致。
- 点击页码后执行:
history.pushState({ page: newPage }, '', `?page=${newPage}`) - 同时监听
window.addEventListener('popstate', e => { if (e.state?.page) gotoPage(e.state.page); }) - 初始加载时也要从
new URL(window.location).searchParams.get('page')读取,而不是默认设为 1 - 注意:
pushState不会触发页面刷新,但popstate会在用户导航时触发,两者必须配对
最易被忽略的是“空数据场景”:当 total = 0 时,Math.ceil(0 / size) 是 0,但分页栏不该消失,应显式设为 1 页,并禁用所有页码按钮——否则用户面对空白分页栏会以为功能坏了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











