Vant Pagination 分页组件完全指南:从基础用法到源码级原理剖析
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
分页(Pagination)是移动端列表数据量过大时的标准解决方案,通过将数据按页拆分、每次只渲染一页,从而显著降低首屏渲染压力与网络传输成本。本文以 Vant 4 的 Pagination 组件为对象,完整覆盖组件注册、四种核心用法(基础分页、简单模式、省略号快速跳转、自定义按钮)、全部 Props/Events/Slots 配置项、类型定义与主题定制方案,并结合 Pagination.tsx 源码与 index.spec.ts 测试用例,深入剖析页码计算、边界收敛与事件派发的底层实现,帮助你在实际业务中熟练配置、深度定制并快速定位问题。
组件引入与注册
Pagination 与其他 Vant 组件一样,支持按需引入与全局注册。在入口文件中通过app.use注册组件后,即可在模板中使用<van-pagination>标签:
import { createApp } from 'vue'; import { Pagination } from 'vant'; const app = createApp(); app.use(Pagination);从源码看,index.ts 使用withInstall包装了组件,并同时导出了paginationProps(供自定义二次封装时复用 Props 定义)、PaginationMode、PaginationProps与PaginationThemeVars类型;此外还通过declare module 'vue'为VanPagination注册了全局组件类型,配合 IDE 可自动获得模板内的类型提示。若你的工程启用了按需引入(如 unplugin-vue-components),则无需手动注册,插件会自动完成导入。
核心用法演示
基础用法:通过 v-model 绑定当前页码
数据总量与每页条数确定后,组件会自动计算总页数。通过v-model绑定当前页码即可:
<van-pagination v-model="currentPage" :total-items="24" :items-per-page="5" />import { ref } from 'vue'; export default { setup() { const currentPage = ref(1); return { currentPage }; }, };示例中total-items为 24、items-per-page为 5,组件会算出总页数为Math.ceil(24 / 5) = 5,首屏渲染出 5 个页码按钮,并默认展示"上一页 / 下一页"按钮(默认文案见下文 Props 说明)。
简单模式:只展示页码描述
将mode设置为simple可切换到简单模式。此时不再渲染具体的页码按钮,而是以"当前页/总页数"的描述形式展示,适合页面空间紧凑的场景:
<van-pagination v-model="currentPage" :page-count="12" mode="simple" />在简单模式下,页面结构为"上一页按钮 + 页码描述 + 下一页按钮"。从 Pagination.tsx 的renderDesc实现看,页码描述默认渲染为`${props.modelValue}/${count.value}`的形式;组件还预留了pageDesc插槽(slots.pageDesc),可在描述文案不能满足需求时自定义展示内容,例如显示"共 12 页"或带图标的信息。简单模式下上一页/下一页按钮会带有边框样式(bem('item', { border: mode === 'simple' })),与多页模式下的无边框外观相区分。
显示省略号:快速跨页跳转
当页数很多、无法一次展示全部页码时,可设置show-page-size限制同时显示的页码个数,并通过force-ellipses开启省略号按钮。点击省略号可一次向前/向后跳转一整组页码:
<van-pagination v-model="currentPage" :total-items="125" :show-page-size="3" force-ellipses />示例中总记录数 125、每页默认 10 条,共 13 页;show-page-size="3"使可见页码窗口为 3 个,首屏与末屏页码不足 3 个时,组件会自动在窗口两端补出省略号按钮。省略号按钮同样遵循"点击跳转一整个窗口"的交互逻辑(详见下文源码原理)。
自定义按钮:插槽全面定制
通过prev-text、next-text插槽可替换上/下一页按钮内容,通过page插槽可定制每一个页码按钮(插槽参数包含页码信息),常用于搭配图标实现更轻量的视觉风格:
<van-pagination v-model="currentPage" :total-items="50" :show-page-size="5"> <template #prev-text> <van-icon name="arrow-left" /> </template> <template #next-text> <van-icon name="arrow" /> </template> <template #page="{ text }">{{ text }}</template> </van-pagination>以上用法均可在 demo/index.vue 中查看完整可运行的示例(该 demo 同时是 demo.spec.ts 快照测试的数据源)。
API 详解
Props 配置项
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model | 当前页码 | number | - |
| mode | 显示模式,可选值为simple | string | multi |
| prev-text | 上一页按钮文字 | string | 上一页 |
| next-text | 下一页按钮文字 | string | 下一页 |
| page-count | 总页数 | number | string | 根据页数计算 |
| total-items | 总记录数 | number | string | 0 |
| items-per-page | 每页记录数 | number | string | 10 |
| show-page-size | 显示的页码个数 | number | string | 5 |
| force-ellipses | 是否显示省略号 | boolean | false |
| show-prev-button | 是否展示上一页按钮 | boolean | true |
| show-next-button | 是否展示下一页按钮 | boolean | true |
对应源码中的 Props 定义见 Pagination.tsx:
export const paginationProps = { mode: makeStringProp<PaginationMode>('multi'), prevText: String, nextText: String, pageCount: makeNumericProp(0), modelValue: makeNumberProp(0), totalItems: makeNumericProp(0), showPageSize: makeNumericProp(5), itemsPerPage: makeNumericProp(10), forceEllipses: Boolean, showPrevButton: truthProp, showNextButton: truthProp, };几个关键点需要特别注意:
page-count的优先级高于total-items / items-per-page:源码中总页数的计算逻辑为const count = +pageCount || Math.ceil(+totalItems / +itemsPerPage),即显式传入page-count时优先使用它,否则才根据总记录数与每页条数推算。total-items为 0 时计算出的总页数为 0,但组件通过Math.max(1, count)将总页数强制收敛为至少 1 页,避免出现空分页器。prev-text/next-text的默认文案与国际化联动:当未传文字时,组件回退到props.prevText || t('prev')。t来自createNamespace('pagination'),会从当前语言包的pagination.prev/pagination.next字段取值——例如 en-US.ts 中为 "Previous"/"Next",中文为"上一页/下一页"。通过 Locale 组件 切换语言后按钮文案会自动跟随,无需额外处理。show-prev-button/show-next-button默认开启(truthProp),置为false可隐藏对应按钮;结合 index.spec.ts 的测试可知,隐藏后对应的.van-pagination__item--prev/.van-pagination__item--nextDOM 节点不会渲染。该特性在"第一页/最后一页不需要翻页入口"或"仅展示页码"等定制场景中很有用。- 所有数值型参数(
page-count、total-items、items-per-page、show-page-size)均为number | string,传入字符串如"5"会被内部通过+运算符正确转换为数值。
Events 事件
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| change | 页码改变时触发 | - |
源码中组件声明了emits: ['change', 'update:modelValue']。需要区分两个事件:点击页码/翻页按钮时,组件先emit('update:modelValue', value)同步 v-model,再emit('change', value)通知业务侧;而change事件仅在"页码真实发生变化"时触发——updateModelValue内部先对目标页码做clamp(value, 1, count.value)边界收敛,并在props.modelValue !== value时才派发事件,因此点击当前页或边界外的页码不会产生多余的事件回调。测试 index.spec.ts 验证了点击第 3 页、上一页、下一页依次触发change且回调值分别为3、2、3。
Slots 插槽
| 名称 | 描述 | 参数 |
|---|---|---|
| page | 自定义页码 | { number: number, text: string, active: boolean } |
| prev-text | 自定义上一页按钮文字 | - |
| next-text | 自定义下一页按钮文字 | - |
page插槽接收三个参数:number为页码数字,text为展示文本(省略号场景下为'...'),active标识当前页是否处于激活态,可在自定义内容中据此做高亮处理。测试 index.spec.ts 演示了page插槽的用法(({ text }) =>foo ${text}``)。此外上文提到,源码中还预留了未在文档表格中列出的pageDesc插槽,用于简单模式下自定义页码描述内容,属额外能力,按需使用即可。
类型定义
组件在包入口导出了以下 TypeScript 类型,便于在组合式 API 或二次封装中声明类型:
import type { PaginationMode, PaginationProps } from 'vant';PaginationMode为'simple' | 'multi'联合类型;PaginationProps由paginationProps通过ExtractPropTypes推导而来(见 Pagination.tsx),与组件 Props 完全同步,避免了手写类型与实现不一致的问题。
主题定制:CSS 变量
组件通过 CSS 变量暴露了完整的定制入口,默认值定义在 index.less,类型声明见 types.ts:
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-pagination-height | 40px | 分页条高度 |
| --van-pagination-font-size | var(--van-font-size-md) | 字体大小 |
| --van-pagination-item-width | 36px | 页码项最小宽度 |
| --van-pagination-item-default-color | var(--van-primary-color) | 页码项文字/激活背景色 |
| --van-pagination-item-disabled-color | var(--van-gray-7) | 禁用态文字颜色 |
| --van-pagination-item-disabled-background | var(--van-background) | 禁用态背景色 |
| --van-pagination-background | var(--van-background-2) | 分页条背景色 |
| --van-pagination-desc-color | var(--van-gray-7) | 页码描述文字颜色 |
| --van-pagination-disabled-opacity | var(--van-disabled-opacity) | 禁用态不透明度 |
使用方式有两种:在全局样式中覆盖变量,或通过 ConfigProvider 组件 按局部作用域动态设置主题。例如把激活页码改成品牌色并压缩高度:
<van-config-provider :theme-vars="{ paginationHeight: '36px', paginationItemDefaultColor: '#ff6b00' }"> <van-pagination v-model="currentPage" :total-items="24" :items-per-page="5" /> </van-config-provider>从样式源码看,激活页码(--active)与按压态(:active)都会将文字变白、背景变为--van-pagination-item-default-color;禁用态则同时应用--van-pagination-item-disabled-color、--van-pagination-item-disabled-background与--van-pagination-disabled-opacity,因此自定义这三个变量即可完整控制上/下一页按钮在首末页的置灰效果。
源码级原理剖析
总页数的计算与收敛
总页数count是组件一切渲染的前提,其计算逻辑(Pagination.tsx)为:
const count = computed(() => { const { pageCount, totalItems, itemsPerPage } = props; const count = +pageCount || Math.ceil(+totalItems / +itemsPerPage); return Math.max(1, count); });即page-count显式传入时直接采用;否则按total-items / items-per-page向上取整。最终经过Math.max(1, ...)收敛,保证至少 1 页。
可见页码窗口算法
当show-page-size小于总页数时,组件不会渲染全部页码,而是计算一个以当前页为中心的可见窗口(Pagination.tsx):
let startPage = 1; let endPage = pageCount; const isMaxSized = showPageSize < pageCount; if (isMaxSized) { // 当前页置于窗口中间 startPage = Math.max(modelValue - Math.floor(showPageSize / 2), 1); endPage = startPage + showPageSize - 1; // 超出末尾时整体回退 if (endPage > pageCount) { endPage = pageCount; startPage = endPage - showPageSize + 1; } }算法要点:正常情况下当前页位于窗口正中(modelValue - floor(showPageSize/2)),贴近首页时用Math.max(..., 1)防止窗口越界;贴近末页时先让endPage封顶为pageCount,再把startPage回退为endPage - showPageSize + 1,保证窗口永远完整落在有效页范围内。
省略号按钮的生成紧随其后:当forceEllipses开启且存在窗口(isMaxSized && showPageSize > 0)时,若窗口起始页大于 1,则在开头插入一个指向startPage - 1的省略号;若窗口结束页小于总页数,则在末尾插入一个指向endPage + 1的省略号。点击省略号即跳转到该目标页,等效于"整组翻页"。
边界处理与事件派发
所有页码切换统一收敛到updateModelValue(Pagination.tsx):
const updateModelValue = (value: number, emitChange?: boolean) => { value = clamp(value, 1, count.value); if (props.modelValue !== value) { emit('update:modelValue', value); if (emitChange) { emit('change', value); } } };- 越界保护:
clamp(value, 1, count.value)将目标页码限制在[1, 总页数]内,首屏点"上一页"、末屏点"下一页"不会产生无效页码。 - 去重派发:仅当目标页码与当前值不同才触发事件,避免重复点击造成无意义的状态更新与请求。
- 双向同步:
watchEffect(() => updateModelValue(props.modelValue))监听外部传入的modelValue,一旦外部重置页码,组件内部状态与渲染随之收敛,无需手动刷新。 - 边界禁用:上一页按钮在
modelValue === 1时置为disabled,下一页按钮在modelValue === count.value时置为disabled(Pagination.tsx 与 Pagination.tsx),保证边界状态下按钮既不可点击、样式也进入禁用态。
组件最终渲染为语义化的<nav role="navigation">结构,内部以<ul>承载各<li>项,页码按钮带aria-current标记当前页(见 Pagination.tsx),对屏幕阅读器友好。
测试覆盖一览
组件的核心行为均有测试保障,相关用例位于 packages/vant/src/pagination/test/,包括:
- index.spec.ts:验证
prev-text/next-text/page插槽渲染、change事件按[3, 2, 3]顺序触发、以及showPrevButton/showNextButton为false时按钮 DOM 不渲染; - demo.spec.ts 与 demo-ssr.spec.ts:对完整 demo 页面做快照测试,同时覆盖了 SSR 渲染场景,保证组件在服务端渲染下输出稳定。
小结
Vant 的 Pagination 组件用极少的配置覆盖了移动端最常见的分页诉求:v-model双向绑定当前页、mode="simple"压缩展示空间、force-ellipses+show-page-size解决长列表页码过多问题、插槽体系支持任意按钮内容定制、CSS 变量与 ConfigProvider 支持深度主题定制。理解其"总页数优先取 page-count、窗口算法保证可见页码居中且不越界、clamp 收敛 + 去重派发保证事件稳定"的三条实现主线,你就能在业务中游刃有余地配置与排查问题。更完整的注册方式说明可参考 组件注册,其余组件的使用可继续浏览 vant 组件源码。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考