扩展 RouterLink | Vue Router
为什么需要扩展 RouterLink?
虽然 Vue Router 提供的 RouterLink 组件已经非常强大,但在某些场景下,你可能需要自定义链接的行为、样式或功能。扩展 RouterLink 允许你创建符合特定需求的导航组件。
使用 v-slot API
RouterLink 提供了强大的 v-slot API,让你可以完全控制链接的渲染:
基本 v-slot 用法
html
<RouterLink
v-slot="{ href, route, navigate, isActive, isExactActive }"
to="/about"
custom
>
<a
:href="href"
@click="navigate"
:class="{ active: isActive }"
>
关于我们
</a>
</RouterLink>插槽参数详解
- href: 解析后的 URL 字符串
- route: 标准化后的路由对象
- navigate: 导航函数,调用后执行路由跳转
- isActive: 是否匹配当前路由(包括祖先路由)
- isExactActive: 是否精确匹配当前路由
自定义链接组件
创建基础自定义链接
vue
<template>
<RouterLink v-slot="{ href, navigate, isActive }" v-bind="$props" custom>
<a
:href="href"
@click="navigate"
:class="[
'custom-link',
isActive && 'active',
isExactActive && 'exact-active'
]"
>
<slot />
</a>
</RouterLink>
</template>
<script setup>
import { RouterLink } from 'vue-router'
defineProps({
to: {
type: [String, Object],
required: true
}
})
</script>
<style scoped>
.custom-link {
text-decoration: none;
color: #333;
transition: color 0.3s;
}
.custom-link.active {
color: #007bff;
font-weight: bold;
}
.custom-link.exact-active {
border-bottom: 2px solid #007bff;
}
</style>支持外部链接的智能组件
vue
<template>
<component
:is="tag"
v-bind="linkProps"
@click="handleClick"
>
<slot />
</component>
</template>
<script setup>
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
const props = defineProps({
to: {
type: [String, Object],
required: true
},
external: {
type: Boolean,
default: false
}
})
const isExternal = computed(() => {
if (props.external) return true
if (typeof props.to === 'string') {
return props.to.startsWith('http')
}
return false
})
const tag = computed(() => isExternal.value ? 'a' : RouterLink)
const linkProps = computed(() => {
if (isExternal.value) {
return {
href: typeof props.to === 'string' ? props.to : props.to.path,
target: '_blank',
rel: 'noopener noreferrer'
}
} else {
return { to: props.to }
}
})
const handleClick = (event) => {
if (isExternal.value) {
// 外部链接,让浏览器处理
return
}
// 内部链接,可以添加自定义逻辑
emit('click', event)
}
const emit = defineEmits(['click'])
</script>高级自定义场景
1. 按钮式导航链接
vue
<template>
<RouterLink
v-slot="{ navigate, isActive }"
:to="to"
custom
>
<button
@click="handleClick(navigate)"
:class="[
'btn-nav',
variantClass,
isActive && 'active'
]"
:disabled="disabled"
>
<slot />
</button>
</RouterLink>
</template>
<script setup>
defineProps({
to: {
type: [String, Object],
required: true
},
variant: {
type: String,
default: 'primary',
validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
},
disabled: Boolean
})
const variantClass = computed(() => `btn-nav--${props.variant}`)
const handleClick = (navigate) => {
if (!props.disabled) {
navigate()
}
}
</script>
<style scoped>
.btn-nav {
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.btn-nav--primary {
background-color: #007bff;
color: white;
}
.btn-nav--secondary {
background-color: #6c757d;
color: white;
}
.btn-nav.active {
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.5);
}
.btn-nav:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>2. 图标导航链接
vue
<template>
<RouterLink
v-slot="{ navigate, isActive }"
:to="to"
custom
>
<div
class="icon-link"
@click="navigate"
:class="{ active: isActive }"
:title="tooltip"
>
<component :is="icon" class="icon" />
<span v-if="showText" class="text">
<slot />
</span>
</div>
</RouterLink>
</template>
<script setup>
defineProps({
to: {
type: [String, Object],
required: true
},
icon: {
type: Object,
required: true
},
tooltip: String,
showText: {
type: Boolean,
default: true
}
})
</script>
<style scoped>
.icon-link {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.icon-link:hover {
background-color: #f8f9fa;
}
.icon-link.active {
background-color: #e3f2fd;
color: #1976d2;
}
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
.text {
font-size: 14px;
}
</style>3. 面包屑导航链接
vue
<template>
<RouterLink
v-slot="{ navigate, isActive }"
:to="to"
custom
>
<span
class="breadcrumb-item"
@click="navigate"
:class="{ active: isActive, last: isLast }"
>
<slot />
<span v-if="!isLast" class="separator">/</span>
</span>
</RouterLink>
</template>
<script setup>
defineProps({
to: {
type: [String, Object],
required: true
},
isLast: {
type: Boolean,
default: false
}
})
</script>
<style scoped>
.breadcrumb-item {
cursor: pointer;
color: #6c757d;
transition: color 0.3s;
}
.breadcrumb-item:hover {
color: #007bff;
}
.breadcrumb-item.active {
color: #495057;
font-weight: bold;
}
.breadcrumb-item.last {
cursor: default;
color: #495057;
}
.breadcrumb-item.last:hover {
color: #495057;
}
.separator {
margin: 0 8px;
color: #dee2e6;
}
</style>性能优化技巧
1. 避免不必要的重新渲染
vue
<template>
<RouterLink
v-slot="{ isActive }"
:to="to"
custom
>
<a
:href="href"
@click="handleClick"
:class="{ active: isActive }"
v-once
>
<slot />
</a>
</RouterLink>
</template>2. 使用记忆化计算
vue
<script setup>
import { computed } from 'vue'
const props = defineProps({
to: [String, Object]
})
const normalizedTo = computed(() => {
if (typeof props.to === 'string') {
return { path: props.to }
}
return props.to
})
const href = computed(() => {
// 记忆化 href 计算
return router.resolve(normalizedTo.value).href
})
</script>最佳实践
- 保持一致性 - 自定义链接组件应该与原生 RouterLink 保持相似的 API
- 可访问性 - 确保自定义链接支持键盘导航和屏幕阅读器
- 测试覆盖 - 为自定义链接组件编写完整的测试用例
- 文档完善 - 为自定义组件提供清晰的使用文档
🎯 总结:通过扩展 RouterLink,你可以创建出既符合设计需求又保持路由功能的强大导航组件,大大提升开发效率和用户体验。