radix-vue PaginationList 组件完全指南:分页项列表的渲染与控制
【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue
本篇技术指南聚焦 radix-vue(原 Radix Vue)组件库中的PaginationList组件,讲解它作为分页条"中间列表"的职责:如何通过作用域插槽获取由PaginationRoot计算好的页码与省略号项目集合,以及as/asChild属性如何控制渲染元素。结合源码实现,你将掌握分页列表的生成算法、与PaginationListItem、PaginationEllipsis的协作方式,并能直接写出可运行、无障碍友好的完整分页组件。
组件定位:Pagination 家族中的"列表容器"
radix-vue 的分页能力由多个组件协作完成,源码位于 packages/core/src/Pagination:
PaginationRoot:管理当前页page、总页数pageCount等状态,并通过注入上下文向子组件提供数据;PaginationList:本文主角,一个纯展示型容器,不直接渲染任何页码按钮,而是通过items作用域插槽把计算好的"分页项目集合"交给使用者渲染;PaginationListItem/PaginationEllipsis:分别渲染单个页码按钮和省略号;PaginationFirst/PaginationPrev/PaginationNext/PaginationLast:首、上一、下一、末页跳转按钮。
在典型的组合中,PaginationList充当分页条的"中间地带"——左右两侧是跳转按钮,中间则是一个由items驱动的循环渲染区。相关源码可对照 PaginationList.vue 与 PaginationRoot.vue。
Props:继承自 Primitive 的两个核心属性
根据官方文档(PaginationList.md),PaginationList的 Props 完全继承自PrimitiveProps,只有两项:
| Name | Description | Type | Required | Default |
|---|---|---|---|---|
as | 组件渲染为的元素或组件,可被asChild覆盖。 | AsTag \| Component | No | "div" |
asChild | 将默认渲染元素替换为传入的子元素,并合并两者的 props 与行为。详情见官方 Composition 指南。 | boolean | No | - |
从实现看,PaginationList.vue 的模板即是一个Primitive包裹:
<Primitive v-bind="props"> <slot :items="transformedRange" /> </Primitive>由于PaginationListProps extends PrimitiveProps,它默认渲染为<div>。实际使用中通常无需修改,因为列表本身只负责提供插槽数据;若需要语义化标签(如ul),可通过as="ul"调整,或在更细粒度上由PaginationListItem各自控制渲染元素。Primitive的实现位于 packages/core/src/Primitive。
Slots:items—— 分页项目集合
文档中定义的本组件唯一插槽:
| Name | Description | Type |
|---|---|---|
items | 分页项目 | { type: "ellipsis"; } \| { type: "page"; value: number; } |
items是一个可辨识联合(discriminated union)数组,每个元素要么是页码项{ type: 'page'; value: number },要么是省略号项{ type: 'ellipsis' }。类型定义见 PaginationList.vue。
结合 story/_Pagination.vue 的官方用法,通过v-slot="{ items }"取出数据后,通常配合v-for循环渲染:
<PaginationList v-slot="{ items }" class="flex items-center gap-2"> <PaginationFirst /> <PaginationPrev /> <template v-for="(page, index) in items"> <PaginationListItem v-if="page.type === 'page'" :key="index" :value="page.value" class="border rounded px-4 py-2>const transformedRange = computed(() => { return transform( getRange( rootContext.page.value, rootContext.pageCount.value, rootContext.siblingCount.value, rootContext.showEdges.value, ), ) })getRange:页码窗口计算
getRange(currentPage, pageCount, siblingCount, showEdges)返回(string | number)[],其中数字代表真实页码、字符串'ellipsis'代表省略号。其核心逻辑(参考自 chakra-ui/zag 的 pagination 工具函数):
showEdges = false(默认):采用"滑动窗口"策略。itemCount = siblingCount * 2 + 1;当总页数小于窗口大小时直接返回全部页码;当前页靠近开头时返回[1..itemCount];靠近末尾时返回末尾的itemCount个页码;位于中间时返回[currentPage - siblingCount .. currentPage + siblingCount]。showEdges = true:始终保留第 1 页、最后 1 页与最多两个省略号。此时totalPageNumbers = min(2 * siblingCount + 5, pageCount),再通过showLeftEllipsis/showRightEllipsis的组合判断,分别产生"左侧省略"、"右侧省略"、"双侧省略"或"全量"四种形态(源码中有详细注释说明2 * siblingCount + 5的构成:两侧兄弟页 ×2、左右省略号 ×2、首尾页 ×2 与当前页 ×1)。
transform:统一为插槽数据类型
transform把(string | number)[]映射为插槽要求的联合类型——数字转{ type: 'page', value },字符串(即'ellipsis')转{ type: 'ellipsis' }。
与 Root 参数的联动
PaginationRoot的默认值(见 PaginationRoot.vue)直接决定了列表形态:
siblingCount默认2:当前页两侧各显示 2 个页码;showEdges默认false:不强制显示首尾页;total默认0,pageCount = Math.max(1, Math.ceil(total / (itemsPerPage || 1))),因此total=0时总页数为 1,所有跳转按钮呈禁用态。
与PaginationListItem/PaginationEllipsis的分工
items只是"数据",真正把数据渲染成可交互元素的分别是:
- PaginationListItem.vue:接收
value属性,内部通过computed判断isSelected(当前页等于自身值),并自动输出无障碍属性——aria-label="Page {value}"、选中时aria-current="page"与data-selected="true",点击时调用上下文中的onPageChange更新页码;在禁用状态下点击事件被拦截(!disabled && rootContext.onPageChange(value))。 - PaginationEllipsis.vue:渲染
data-type="ellipsis"的占位元素,默认内容为省略号字符…。
测试用例 Pagination.test.ts 验证了这些行为:默认首屏第 1 页data-selected="true"、点击"下一页"后选中态切换到第 2 页、showEdges开启后第 1 页与第 10 页始终存在,且全部通过vitest-axe无障碍检查。
完整实战示例
将以上内容组合,一个完整的分页条(数据共 100 条、每页 10 条、共 10 页)如下:
<script setup lang="ts"> import { ref } from 'vue' import { PaginationEllipsis, PaginationFirst, PaginationLast, PaginationList, PaginationListItem, PaginationNext, PaginationPrev, PaginationRoot, } from 'radix-vue' const page = ref(1) </script> <template> <PaginationRoot v-model:page="page" :total="100" :items-per-page="10"> <PaginationList v-slot="{ items }" class="flex items-center gap-2"> <PaginationFirst /> <PaginationPrev /> <template v-for="(item, index) in items" :key="index"> <PaginationListItem v-if="item.type === 'page'" :value="item.value" class="border rounded px-4 py-2 contenteditable="false">【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue
项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考