Vant Sidebar 侧边导航组件完全指南:用法、API 与源码级原理解析
2026/9/12 22:57:24 网站建设 项目流程

Vant Sidebar 侧边导航组件完全指南:用法、API 与源码级原理解析

【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant

导读

本文围绕 Vant 4 移动端 UI 库中的Sidebar(侧边导航)组件展开,它提供一种垂直展示的导航栏,用于在不同内容区域之间快速切换,常见于分类页、设置页、商品列表筛选等场景。通过本文你将掌握van-sidebar/van-sidebar-item的完整用法(基础绑定、徽标、禁用、事件监听、路由跳转与自定义插槽)、全部 Props / Events / Slots / CSS 变量 API,并从源码与测试层面理解其"父子组件通过依赖注入协同工作"的底层原理,最终能够独立完成样式定制与二次开发。

一、组件定位与引入方式

1. 组件定位

Sidebar 是 Vant 中为数不多的"垂直导航"类组件:它默认渲染一个宽度约 80px 的窄列,导航项自上而下排列,选中项左侧会显示一条主题色竖条,适合放在页面左侧(或内容区上方)配合右侧内容区联动切换。官方文档将其定位概括为:"垂直展示的导航栏,用于在不同的内容区域之间进行切换。"

在 Vant 的组件体系中,Sidebar 由两个组件协作完成:

  • Sidebar(容器):负责管理选中索引状态,通过v-model对外双向同步;
  • SidebarItem(导航项):渲染单个导航条目,负责点击交互、徽标展示与路由跳转。

2. 安装与全局注册

组件随vant主包一起发布,无需单独安装。通过app.use进行全局注册:

import { createApp } from 'vue'; import { Sidebar, SidebarItem } from 'vant'; const app = createApp(); app.use(Sidebar); app.use(SidebarItem);

全局注册后,模板中即可直接使用<van-sidebar><van-sidebar-item>标签。Vant 还支持按需引入(配合vant-auto-import-resolverunplugin-vue-components)、手动局部注册等多种方式,更多注册方式可参考 组件注册。

二、基础用法:v-model 双向绑定选中项

Sidebar 的核心交互就是"当前选中哪一项"。通过v-model绑定当前选中项的索引(数字或字符串),默认值为0,即默认选中第一项:

<van-sidebar v-model="active"> <van-sidebar-item title="标签名称" /> <van-sidebar-item title="标签名称" /> <van-sidebar-item title="标签名称" /> </van-sidebar>
import { ref } from 'vue'; export default { setup() { const active = ref(0); return { active }; }, };

也可以使用<script setup>写法:

<script setup> import { ref } from 'vue'; const active = ref(0); </script> <template> <van-sidebar v-model="active"> <van-sidebar-item title="商品分类" /> <van-sidebar-item title="优惠活动" /> <van-sidebar-item title="我的订单" /> </van-sidebar> </template>

源码视角:v-model 是如何工作的

在 Sidebar.tsx 中,Sidebar的 props 定义极其精简——只有一个modelValue

export const sidebarProps = { modelValue: makeNumericProp(0), };

makeNumericProp(0)表示该属性接受number | string类型,默认值为0,这与文档中v-model类型_number | string_、默认值0一一对应。

组件通过linkChildren将两个方法注入给所有子级SidebarItem

const getActive = () => +props.modelValue; const setActive = (value: number) => { if (value !== getActive()) { emit('update:modelValue', value); emit('change', value); } }; linkChildren({ getActive, setActive });

关键细节:

  • getActive用一元运算符+把字符串索引强制转为数字,保证内部比较与高亮判断类型一致;
  • setActive只在值确实发生变化时才触发update:modelValue(驱动v-model更新)和change事件,避免无意义的重复触发——这一点在后面的测试用例中也有印证。

三、徽标提示:dot 与 badge

SidebarItem 内置了 Badge 徽标能力,支持两种展示形式:

  • dot:在标题右上角展示一个小红点(布尔值,默认false);
  • badge:在标题右上角展示徽标内容(number | string,支持数字角标或自定义文本)。
<van-sidebar v-model="active"> <van-sidebar-item title="标签名称" dot /> <van-sidebar-item title="标签名称" badge="5" /> <van-sidebar-item title="标签名称" /> </van-sidebar>

进阶:badge-props 透传 Badge 属性

如果默认徽标样式不够用,可以通过badge-props把任意 Badge 组件 的属性透传进去,例如自定义徽标颜色:

<van-sidebar v-model="active"> <van-sidebar-item title="消息" :badge="99" :badge-props="{ color: '#1989fa' }" /> <van-sidebar-item title="任务" dot /> </van-sidebar>

源码视角:徽标如何渲染

在 SidebarItem.tsx 中,标题被包裹在Badge组件内部:

<Badge dot={dot} class={bem('text')} content={badge} {...props.badgeProps} > {slots.title ? slots.title() : title} </Badge>

可以看到badge-props通过展开运算符{...props.badgeProps}直接透传给 Badge。测试文件 index.spec.tsx 中专门验证了这条链路:

test('should render badge-props correctly', () => { // ... <SidebarItem badge={1} badgeProps={{ color: 'blue' }} /> // ... expect(badge.style.backgroundColor).toEqual('blue'); });

badgeProps={{ color: 'blue' }}会真实作用到渲染出的.van-badge元素的背景色上。

四、禁用选项

通过disabled属性可以禁用某个导航项。被禁用的项点击无响应、不会触发切换,样式上会使用禁用态颜色并将鼠标光标变为not-allowed

<van-sidebar v-model="active"> <van-sidebar-item title="标签名称" /> <van-sidebar-item title="标签名称" disabled /> <van-sidebar-item title="标签名称" /> </van-sidebar>

源码视角:禁用如何拦截点击

SidebarItem 的点击处理逻辑非常直接:

const onClick = () => { if (props.disabled) { return; } emit('click', index.value); parent.setActive(index.value); route(); };

disabled时直接return,既不会触发click事件,也不会调用parent.setActive更新选中状态。测试用例 index.spec.tsx 也验证了这一点:

test('should not update v-model when disabled SidebarItem is clicked', () => { // 点击 index=1 的 disabled 项后 expect(wrapper.vm.active).toEqual(0); // v-model 保持 0 不变 });

五、监听切换事件:change 与 click

Sidebar 提供两个与交互相关的事件:

  • change(Sidebar 级别):选中项变化时触发,回调参数为选中项索引index: number
  • click(SidebarItem 级别):点击某个导航项时触发,回调参数同样为该索引。
<van-sidebar v-model="active" @change="onChange"> <van-sidebar-item title="标签名 1" /> <van-sidebar-item title="标签名 2" /> <van-sidebar-item title="标签名 3" /> </van-sidebar>
import { ref } from 'vue'; import { showToast } from 'vant'; export default { setup() { const active = ref(0); const onChange = (index) => showToast(`标签名 ${index + 1}`); return { active, onChange, }; }, };

源码视角:两个事件的分工与触发时机

从上面的onClick源码可以看出触发顺序为:先emit('click', index.value)(SidebarItem 的 click),再parent.setActive(index.value)(其内部再触发 Sidebar 的change)。也就是说,一次点击会依次触发click事件和change事件;但change仅在选中索引真正改变时触发(setActive内部有value !== getActive()判断)。

测试用例对这套行为做了精确断言:

test('should emit change event when active item changed', () => { // 点击第 0 项:因已是选中项,change 不触发 expect(onChange).toHaveBeenCalledTimes(0); // 点击第 1 项:change 触发且参数为 1 items[1].trigger('click'); expect(onChange).toHaveBeenCalledWith(1); }); test('should emit click event when SidebarItem is clicked', () => { wrapper.find('.van-sidebar-item').trigger('click'); expect(onClick).toHaveBeenCalledWith(0); // 点击事件始终触发(未禁用时) });

实践中两者的选型建议:需要"联动内容区刷新"时监听 Sidebar 的change;需要"统计点击行为/处理单项特例"时监听 SidebarItem 的click

六、标题插槽与路由跳转

1. title 插槽自定义内容

如果标题不只是纯文本(例如要嵌入图标、富文本或模板片段),可以使用title插槽完全接管标题渲染:

<van-sidebar v-model="active"> <van-sidebar-item> <template #title> <van-icon name="wap-home-o" /> 首页 </template> </van-sidebar-item> <van-sidebar-item title="分类" /> </van-sidebar>

使用插槽时title属性会被忽略(源码中slots.title ? slots.title() : title的优先级顺序即为此意)。测试 index.spec.tsx 中也有对应快照用例。

2. 路由与链接跳转

SidebarItem 继承了 Vant 统一的routeProps(定义见 use-route.ts),支持三种跳转方式:

属性类型说明
urlstring点击后跳转的链接地址(原生跳转)
tostring \| object跳转的目标路由,等同于 Vue Router 的to属性
replaceboolean是否在跳转时替换当前页面历史记录,默认false
<van-sidebar v-model="active"> <van-sidebar-item title="关于我们" url="https://example.com/about" /> <van-sidebar-item title="个人中心" :to="{ name: 'user' }" /> <van-sidebar-item title="设置" to="/settings" replace /> </van-sidebar>

其底层实现在点击时调用route()

export function route({ to, url, replace, $router: router }) { if (to && router) { routerreplace ? 'replace' : 'push'; } else if (url) { replace ? location.replace(url) : (location.href = url); } }

可见跳转优先级为:to(需要应用已注册 Vue Router)优先于url(原生页面跳转);replace=true时分别对应router.replacelocation.replace,不会在历史栈中留下记录。

七、完整 API 参考

Sidebar Props

参数说明类型默认值
v-model当前导航项的索引number \| string0

Sidebar Events

事件名说明回调参数
change切换导航项时触发index: number

SidebarItem Props

参数说明类型默认值
title内容string''
dot是否显示右上角小红点booleanfalse
badge图标右上角徽标的内容number \| string-
badge-props自定义徽标的属性,透传给 Badge 组件的 propsBadgeProps-
disabled是否禁用该项booleanfalse
url点击后跳转的链接地址string-
to点击后跳转的目标路由对象,等同于 Vue Router 的to属性string \| object-
replace是否在跳转时替换当前页面历史booleanfalse

SidebarItem Events

事件名说明回调参数
click点击时触发index: number

SidebarItem Slots

名称说明
title自定义标题

类型定义

组件导出以下 TypeScript 类型,便于在业务代码中获得完整的类型提示:

import type { SidebarProps, SidebarItemProps } from 'vant';

同时 Vant 还导出了样式变量类型SidebarThemeVars/SidebarItemThemeVars(见 sidebar/types.ts 与 sidebar-item/types.ts),配合 ConfigProvider 的theme-vars使用时可获得键名校验。

八、主题定制:CSS 变量全解析

Sidebar 系列组件提供了 13 个 CSS 变量用于样式定制,所有变量均在 sidebar-item/index.less 的:root/:host中声明(--van-sidebar-width声明于 sidebar/index.less),默认值引用了 Vant 全局设计变量,保证视觉体系一致:

名称默认值作用
--van-sidebar-width80px侧边导航整体宽度
--van-sidebar-font-sizevar(--van-font-size-md)导航项文字大小
--van-sidebar-line-heightvar(--van-line-height-md)导航项行高
--van-sidebar-text-colorvar(--van-text-color)导航项文字颜色
--van-sidebar-disabled-text-colorvar(--van-text-color-3)禁用态文字颜色
--van-sidebar-padding20px var(--van-padding-sm)导航项内边距
--van-sidebar-active-colorvar(--van-active-color)按压(active)态背景色
--van-sidebar-backgroundvar(--van-background)导航项背景色
--van-sidebar-selected-font-weightvar(--van-font-bold)选中项字重
--van-sidebar-selected-text-colorvar(--van-text-color)选中项文字颜色
--van-sidebar-selected-border-width4px选中态左侧竖条宽度
--van-sidebar-selected-border-height16px选中态左侧竖条高度
--van-sidebar-selected-border-colorvar(--van-primary-color)选中态左侧竖条颜色
--van-sidebar-selected-backgroundvar(--van-background-2)选中项背景色

两种定制方式

方式一:CSS 覆盖(最简单,直接覆盖同名变量):

.van-sidebar { --van-sidebar-width: 96px; --van-sidebar-selected-border-color: #ff976a; }

方式二:通过 ConfigProvider 全局定制(主题化方案,变量会作用于子树内所有组件,参见 ConfigProvider 组件):

<van-config-provider :theme-vars="themeVars"> <van-sidebar v-model="active"> <van-sidebar-item title="标签名称" /> </van-sidebar> </van-config-provider>
import { ref } from 'vue'; export default { setup() { const active = ref(0); const themeVars = { sidebarWidth: '100px', sidebarSelectedBorderColor: '#ee0a24', }; return { active, themeVars }; }, };

需要说明的是:选中态左侧竖条通过&--select::before伪元素实现(绝对定位于左侧、垂直居中,宽高分别由--van-sidebar-selected-border-width/--van-sidebar-selected-border-height控制),因此调整竖条粗细时只需修改这两个变量即可。

九、源码级联动原理:provide / inject 协作模型

理解 Sidebar 的内部机制,关键在两点:父组件如何管理状态、子组件如何感知父组件。

1. 父子通信链路

在 Sidebar.tsx 中,Sidebar通过@vant/useuseChildren向所有子级注入getActive/setActive

export type SidebarProvide = { getActive: () => number; setActive: (value: number) => void; }; export const SIDEBAR_KEY: InjectionKey<SidebarProvide> = Symbol(name); // Sidebar setup 内 const { linkChildren } = useChildren(SIDEBAR_KEY); linkChildren({ getActive, setActive });

在 SidebarItem.tsx 中,子组件通过useParent(SIDEBAR_KEY)反向获取父级实例与自身索引:

const { parent, index } = useParent(SIDEBAR_KEY); if (!parent) { if (process.env.NODE_ENV !== 'production') { console.error('[Vant] <SidebarItem> must be a child component of <Sidebar>.'); } return; }

注意这段健壮性处理:当SidebarItem被错误地放置在Sidebar之外时,开发环境会输出明确的错误提示,避免静默失效。

2. 选中态与无障碍语义

  • 选中判断:const selected = index.value === parent.getActive();——由子组件自行比对索引并添加van-sidebar-item--select类;
  • 无障碍:Sidebar根节点带role="tablist",每个SidebarItemrole="tab"aria-selected(当前选中态)与tabindex(禁用时移除焦点能力),天然符合 ARIA 标签页(Tabs)语义,方便读屏软件识别。

3. 滚动与文本细节

  • 容器样式(sidebar/index.less)设置了overflow-y: auto-webkit-overflow-scrolling: touch,导航项较多时容器内部可独立滚动;
  • 标题文本.van-sidebar-item__text设置了word-break: break-all(对应 Vant issue #7455),避免超长标题撑破布局;
  • 导航项之间通过:not(:last-child)::after绘制 1px 分隔线(利用 Vant 全局 hairline 机制)。

十、从 Demo 与测试看真实应用

1. 官方 Demo 的结构

官方演示页面 demo/index.vue 用van-grid将四个场景(基础用法 / 徽标提示 / 禁用选项 / 监听切换事件)并排展示,并配合showToast演示 change 回调,是快速上手组件形态的最佳参考。

2. 测试用例覆盖的行为契约

sidebar/test/index.spec.tsx 覆盖了组件的核心契约,可作为业务开发的"行为文档":

  • 点击当前已选中项不触发change(去重逻辑);
  • 切换选中项触发change且参数为正确索引;
  • 点击非禁用项触发click
  • v-model随点击正确更新,且change只触发一次;
  • 点击disabled项不更新v-model
  • title插槽与badge-props透传均正常渲染。

结语

Sidebar 是 Vant 组件库中"小而精"的典型:外部 API 仅有v-model与少量 Props,内部却依托useChildren/useParent的依赖注入模型、Badge 组件复用与统一的路由封装(useRoute)实现了完整且健壮的导航交互。掌握其用法与原理后,无论是快速接入(5 分钟即可落地一个可用的侧边导航)、深度定制(CSS 变量 + ConfigProvider 主题)还是二次扩展(新增插槽、路由联动),都有清晰的实现路径可循。

相关文件索引:

  • 组件实现:Sidebar.tsx、SidebarItem.tsx
  • 类型定义:sidebar/types.ts、sidebar-item/types.ts
  • 样式:sidebar/index.less、sidebar-item/index.less
  • 测试:sidebar/test/index.spec.tsx
  • 演示:sidebar/demo/index.vue

【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询