Vue Router 4 核心机制与实战
Vue Router 4 核心机制与实战
路由基础配置
Vue Router 4 为 Vue 3 提供了声明式导航能力,通过 createRouter 构建路由实例:
<div id="root">
<nav>
<router-link to="/dashboard">仪表盘</router-link>
<router-link to="/profile">个人中心</router-link>
</nav>
<router-view></router-view>
</div>
<script>
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
const Dashboard = { template: '<div>仪表盘页面</div>' }
const Profile = { template: '<div>个人中心页面</div>' }
const routeTable = [
{ path: '/dashboard', component: Dashboard },
{ path: '/profile', component: Profile }
]
const appRouter = createRouter({
history: createWebHistory(),
routes: routeTable
})
createApp({}).use(appRouter).mount('#root')
</script>
嵌套路由结构
通过 children 属性可构建层级化路由视图,实现页面区域的局部更新:
const Layout = {
template: `
<div class="wrapper">
<aside>
<router-link to="/workspace/task">任务列表</router-link>
<router-link to="/workspace/stat">数据统计</router-link>
</aside>
<main>
<router-view></router-view>
</main>
</div>
`
}
const TaskList = { template: '<div>任务列表内容</div>' }
const Statistics = { template: '<div>数据统计内容</div>' }
const nestedRoutes = [
{
path: '/workspace',
component: Layout,
redirect: '/workspace/task',
children: [
{ path: 'task', component: TaskList },
{ path: 'stat', component: Statistics }
]
}
]
参数传递模式
动态路径参数
在路径中预留占位符,参数值会成为路由路径的组成部分,刷新页面不会丢失:
<!-- 模板中的导航链接 -->
<router-link :to="`/product/${itemCode}/${itemSlug}`">查看详情</router-link>
<!-- 组件内获取参数 -->
<script>
export default {
mounted() {
console.log(this.$route.params.itemCode)
console.log(this.$route.params.itemSlug)
}
}
</script>
路由定义:
{
path: '/product/:sku/:alias',
component: ProductDetail,
props: true // 开启 props 解耦
}
查询字符串参数
适用于可选过滤条件、搜索关键词等场景:
<!-- 导航时携带查询参数 -->
<router-link :to="{
path: '/search',
query: { keyword: searchText, page: currentPage }
}">搜索</router-link>
<!-- 组件内读取 -->
<p>关键词: {{ $route.query.keyword }}</p>
<p>页码: {{ $route.query.page }}</p>
两种参数模式对比
| 特性 | 动态路径参数 | 查询字符串 |
|---|---|---|
| URL 形态 | /user/42/orders |
/user?id=42&tab=orders |
| 路由声明 | 需预先定义 :param |
无需额外声明 |
| 参数必要性 | 必须提供,否则匹配失败 | 完全可选 |
| 刷新行为 | 参数持久化在 URL 中 | 参数持久化在 URL 中 |
编程化导航
脱离模板约束,在业务逻辑中灵活控制跳转流向:
export default {
data() {
return {
authToken: localStorage.getItem('token'),
targetPath: '/checkout'
}
},
methods: {
async handlePurchase() {
if (!this.authToken) {
// 未登录时携带目标地址跳转登录页
this.$router.push({
path: '/login',
query: { redirect: this.targetPath }
})
return
}
try {
await submitOrder()
// 替换当前历史记录,防止回退到提交中状态
this.$router.replace('/order-success')
} catch (err) {
this.$router.push({
path: '/error',
state: { message: err.message }
})
}
},
navigateHistory(steps) {
// 正数前进,负数后退
this.$router.go(steps)
}
}
}
路由侦听与响应
利用组合式 API 对路由变化做出精细化响应:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
setup() {
const currentRoute = useRoute()
// 监听路由参数变化,触发数据重新获取
watch(
() => currentRoute.params.projectId,
async (newId, oldId) => {
if (newId !== oldId) {
await fetchProjectData(newId)
}
},
{ immediate: true }
)
// 监听查询条件变化
watch(
() => currentRoute.query.filterType,
(type) => {
updateFilterDisplay(type)
}
)
}
}
导航守卫体系
Vue Router 提供了多层次的拦截机制,用于权限校验、数据预取等场景:
// 全局前置守卫:每次路由切换前执行
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const hasPermission = checkUserAccess(to.meta.role)
if (requiresAuth && !isLoggedIn()) {
next({ path: '/signin', query: { next: to.fullPath } })
} else if (to.meta.role && !hasPermission) {
next({ path: '/forbidden' })
} else {
next()
}
})
// 全局后置钩子:用于分析、修改标题等副作用
router.afterEach((to) => {
document.title = to.meta.title || '默认标题'
})
// 路由独享守卫
{
path: '/admin',
component: AdminPanel,
beforeEnter: (to, from, next) => {
// 针对该路由的专属校验逻辑
if (maintenanceMode) {
next('/maintenance')
} else {
next()
}
}
}
组件内守卫
在组件选项中定义生命周期式的导航钩子:
export default {
// 进入组件前,无法访问 this,需通过 next 回调传递数据
beforeRouteEnter(to, from, next) {
fetchPreviewData(to.params.id).then(data => {
next(vm => {
vm.previewData = data
})
})
},
// 当前组件被复用时触发
beforeRouteUpdate(to, from, next) {
this.loadNewContent(to.params.id)
next()
},
// 离开前确认,常用于表单防误触
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges) {
const confirmed = confirm('有未保存的更改,确定要离开吗?')
next(confirmed)
} else {
next()
}
}
}