在Vue 3的Composition API生态中,路由管理方式发生了显著变化。传统Options API中的this.$router和this.$route访问方式,在组合式函数中已被更符合响应式理念的useRouter和useRoute替代。这两个hook函数为开发者提供了更灵活的路由控制能力,特别是在需要复用路由逻辑的场景下展现出明显优势。
实际开发中,我注意到许多从Vue 2迁移过来的开发者容易陷入"this依赖症",在setup函数中下意识地尝试访问组件实例。而组合式API的设计哲学正是要打破这种绑定,使路由逻辑可以像普通JavaScript函数一样被封装和复用。下面这个对比示例能清晰展示新旧写法的区别:
javascript复制// Options API方式
export default {
methods: {
navigateToHome() {
this.$router.push('/')
}
}
}
// Composition API方式
import { useRouter } from 'vue-router'
export function useNavigation() {
const router = useRouter()
const navigateToHome = () => {
router.push('/')
}
return { navigateToHome }
}
useRouter返回的路由实例包含完整的编程式导航方法集,这些方法在SPA开发中扮演着关键角色。经过多个项目的实践验证,我总结出最常用的几种导航方式及其适用场景:
路径跳转:router.push('/user')
命名路由:router.push({ name: 'user', params: { id: 123 } })
替换当前路由:router.replace('/login')
历史记录操作:router.go(1)
关键提示:所有导航方法都返回Promise,这意味着你可以优雅地处理导航完成后的逻辑:
javascript复制router.push('/dashboard') .then(() => { // 导航完成后执行 trackPageView() }) .catch(() => { // 导航被中止时执行 showNavigationError() })
在复杂应用中,我们经常需要处理更精细的路由控制。以下是几个经过实战检验的高级模式:
路由拦截与鉴权:
javascript复制const router = useRouter()
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated.value) {
return { path: '/login', query: { redirect: to.fullPath } }
}
})
滚动行为定制:
javascript复制const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else if (to.hash) {
return { el: to.hash }
} else {
return { top: 0 }
}
}
})
路由懒加载优化:
javascript复制const router = createRouter({
routes: [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue'),
meta: { preload: true } // 自定义预加载标记
}
]
})
useRoute返回一个响应式路由对象,其价值在于能够自动追踪路由变化。与Vue 2时代需要手动watch不同,现在可以天然地结合Composition API的响应式特性:
javascript复制import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
// 响应式获取query参数
const searchKeyword = computed(() => route.query.q || '')
// 响应式获取params参数
const userId = computed(() => route.params.id)
在后台管理系统开发中,我经常遇到需要根据URL参数刷新数据的场景。传统方案需要在created和watch中分别处理,而现在可以简化为:
javascript复制import { watchEffect } from 'vue'
watchEffect(() => {
if (route.params.id) {
fetchUserDetails(route.params.id)
}
})
路由元信息(meta)是经常被低估的强大功能。在最近的项目中,我们用它实现了:
javascript复制{
path: '/admin',
meta: { requiresAdmin: true }
}
javascript复制watchEffect(() => {
document.title = route.meta.title || '默认标题'
})
javascript复制<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
真正的威力在于将路由逻辑封装为可复用的组合函数。例如,我们可以创建usePagination:
javascript复制export function usePagination() {
const route = useRoute()
const router = useRouter()
const currentPage = computed({
get: () => Number(route.query.page) || 1,
set: (val) => {
router.replace({
query: { ...route.query, page: val > 1 ? val : undefined }
})
}
})
return { currentPage }
}
在组件中使用:
javascript复制const { currentPage } = usePagination()
watch(currentPage, (newVal) => {
fetchData(newVal)
})
javascript复制// 错误做法 - 每次调用都会创建新实例
const router = useRouter()
const route = useRoute()
// 正确做法 - 在setup顶层调用一次
export default {
setup() {
const router = useRouter()
const route = useRoute()
// 其他逻辑...
}
}
javascript复制const id = computed(() => Number(route.params.id))
javascript复制// 不推荐 - 可能造成多次触发
watch(() => route.params.id, (newId) => {
// ...
})
// 推荐 - 更精确的控制
watch(() => ({
id: route.params.id,
type: route.query.type
}), ({ id, type }) => {
// 同时处理params和query变化
}, { immediate: true })
当连续快速点击导航按钮时,可能会触发多次导航。解决方案:
javascript复制let isNavigating = false
const safePush = async (location) => {
if (isNavigating) return
isNavigating = true
try {
await router.push(location)
} finally {
isNavigating = false
}
}
当动态路由参数变化但组件不刷新时,通常需要:
javascript复制watch(() => route.params.id, (newVal) => {
// 强制更新组件
})
或者使用key策略:
javascript复制<router-view :key="route.fullPath" />
在路由守卫中进行API请求时,务必正确处理异步:
javascript复制router.beforeEach(async (to) => {
if (to.meta.requiresAuth) {
// 等待权限检查完成
return await checkAuth()
}
})
对于TypeScript项目,可以增强路由类型提示:
typescript复制declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
title?: string
transition?: string
}
}
这样在使用route.meta时就能获得完善的类型提示,大大减少拼写错误。
在大型项目中,我们还创建了路由配置工厂函数来保证类型安全:
typescript复制function defineRoute(config: {
path: string
name: string
meta?: RouteMeta
}) {
return config
}
const routes = [
defineRoute({
path: '/',
name: 'Home',
meta: { title: '首页' }
})
]
测试使用路由hook的组件时,需要模拟路由环境:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: []
})
test('uses router', async () => {
const wrapper = mount(Component, {
global: {
plugins: [router]
}
})
await wrapper.find('button').trigger('click')
expect(router.push).toHaveBeenCalledWith('/target')
})
在测试独立于组件的路由逻辑时:
javascript复制import { useRouter } from 'vue-router'
jest.mock('vue-router', () => ({
useRouter: jest.fn(() => ({
push: jest.fn()
}))
}))
test('navigates on click', () => {
const { navigate } = useNavigation()
navigate()
expect(useRouter().push).toHaveBeenCalled()
})
在状态管理中集成路由信息可以创建更强大的逻辑:
javascript复制// stores/route.js
export const useRouteStore = defineStore('route', () => {
const route = useRoute()
const currentRouteName = computed(() => route.name)
const queryParams = computed(() => route.query)
return { currentRouteName, queryParams }
})
这样在任何组件中都可以访问响应式的路由信息:
javascript复制const routeStore = useRouteStore()
const searchQuery = computed(() => routeStore.queryParams.q)
在微前端架构中,路由处理需要特别注意:
javascript复制// 主应用
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/app1/*',
component: () => import('@/layouts/MicroApp.vue'),
meta: { microApp: 'app1' }
}
]
})
// 子应用
let childRouter
if (window.__POWERED_BY_QIANKUN__) {
childRouter = createRouter({
history: createMemoryHistory(),
routes
})
} else {
childRouter = createRouter({
history: createWebHistory(),
routes
})
}
开发时可以在app实例上暴露路由方便调试:
javascript复制const app = createApp(App)
const router = createRouter(/*...*/)
app.config.globalProperties.$router = router
if (import.meta.env.DEV) {
window.router = router
}
监控导航耗时:
javascript复制router.beforeEach((to, from, next) => {
const startTime = performance.now()
next()
const duration = performance.now() - startTime
if (duration > 200) {
console.warn(`Slow navigation to ${to.path}: ${duration}ms`)
}
})
对于需要深度优化的项目,可以考虑实现路由预加载策略:
javascript复制const preloadRoutes = ['/dashboard', '/settings']
preloadRoutes.forEach(path => {
const route = router.resolve(path)
if (route.matched.length) {
const component = route.matched[0].components.default
if (typeof component === 'function') {
component()
}
}
})
在Vue 3的组合式世界中,useRouter和useRoute不仅仅是工具函数,更是连接组件与路由系统的桥梁。经过多个项目的实践,我发现合理运用这些API可以显著提升代码的可维护性和可测试性。特别是在需要共享路由逻辑的场合,组合式函数的表现远超传统的mixin方案。