动态路由与路由守卫
Vue Router 4 的动态路由和路由守卫是构建复杂应用的关键特性。动态路由允许根据参数或条件动态加载页面,而路由守卫则提供导航控制,用于权限验证、数据预加载等场景。本节将深入讲解这两者的实现方式、使用方法和应用场景,并通过示例展示如何在 Vue 3 项目中应用它们。
动态路由
动态路由通过路径参数或查询参数实现灵活的页面渲染,适用于需要根据用户输入或数据动态显示内容的场景。
基本用法
定义动态路由
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
{
path: '/product/:id',
name: 'Product',
component: () => import('@/views/Product.vue')
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
使用参数
<!-- src/views/Product.vue -->
<template>
<div>
<h1>产品 ID: {{ productId }}</h1>
<p>产品名称: {{ productName }}</p>
<button @click="goBack">返回</button>
</div>
</template>
<script>
import { useRoute, useRouter } from 'vue-router';
import { ref, onMounted } from 'vue';
export default {
setup() {
const route = useRoute();
const router = useRouter();
const productId = ref(route.params.id);
const productName = ref('');
// 模拟数据加载
onMounted(() => {
const products = {
'1': '苹果',
'2': '香蕉',
'3': '橙子'
};
productName.value = products[productId.value] || '未知产品';
});
const goBack = () => router.go(-1);
return { productId, productName, goBack };
}
};
</script>
- 效果:访问
/product/1显示“产品 ID: 1”和“产品名称: 苹果”。
动态导航
<!-- src/App.vue -->
<template>
<div>
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'Product', params: { id: 2 } }">产品 2</router-link>
<router-view />
</div>
</template>
多参数与可选参数
支持多个动态参数和可选参数:
const routes = [
{
path: '/user/:userId/post/:postId?', // postId 可选
name: 'UserPost',
component: () => import('@/views/UserPost.vue')
}
];
<!-- src/views/UserPost.vue -->
<template>
<div>
<h1>用户: {{ userId }}</h1>
<p>帖子: {{ postId || '未指定' }}</p>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import { ref } from 'vue';
export default {
setup() {
const route = useRoute();
const userId = ref(route.params.userId);
const postId = ref(route.params.postId);
return { userId, postId };
}
};
</script>
- 访问:
/user/123显示“用户: 123”和“帖子: 未指定”。/user/123/post/456显示“用户: 123”和“帖子: 456”。
通配符路由
捕获所有未匹配路径:
const routes = [
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/NotFound.vue') }
];
<!-- src/views/NotFound.vue -->
<template>
<h1>404 - 页面未找到</h1>
</template>
- 效果:访问
/random/path显示 404 页面。
路由守卫
路由守卫用于控制导航行为,可在全局、路由级别或组件内实现。
1. 全局守卫
前置守卫(beforeEach)
// src/router/index.js
router.beforeEach((to, from, next) => {
console.log(`从 ${from.path} 到 ${to.path}`);
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login'); // 未登录跳转
} else {
next(); // 放行
}
});
function isAuthenticated() {
return !!localStorage.getItem('token'); // 模拟认证
}
- 配置:
const routes = [ { path: '/admin', meta: { requiresAuth: true }, component: () => import('@/views/Admin.vue') }, { path: '/login', component: () => import('@/views/Login.vue') } ]; - 效果:访问
/admin时,未登录跳转到/login。
后置守卫(afterEach)
router.afterEach((to, from) => {
console.log(`导航到 ${to.path} 完成`);
});
2. 路由独享守卫
在特定路由上定义:
const routes = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
beforeEnter: (to, from, next) => {
if (isAuthenticated()) next();
else next('/login');
}
}
];
3. 组件内守卫
在组件内使用:
<!-- src/views/Admin.vue -->
<template>
<h1>管理面板</h1>
</template>
<script>
import { useRouter } from 'vue-router';
export default {
setup() {
const router = useRouter();
router.beforeEach((to, from, next) => {
if (to.path === '/admin' && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
}
};
</script>
- 注意:组件内守卫需谨慎使用,避免重复注册。
综合示例
路由配置
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
{
path: '/product/:id',
name: 'Product',
component: () => import('@/views/Product.vue'),
meta: { requiresAuth: true }
},
{ path: '/login', name: 'Login', component: () => import('@/views/Login.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/NotFound.vue') }
];
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token');
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login');
} else {
next();
}
});
export default router;
组件实现
Home.vue
<template>
<div>
<h1>首页</h1>
<router-link :to="{ name: 'Product', params: { id: 1 } }">产品 1</router-link>
</div>
</template>
Product.vue
<template>
<div>
<h1>产品 {{ route.params.id }}</h1>
<button @click="logout">退出</button>
</div>
</template>
<script>
import { useRoute, useRouter } from 'vue-router';
export default {
setup() {
const route = useRoute();
const router = useRouter();
const logout = () => {
localStorage.removeItem('token');
router.push('/login');
};
return { route, logout };
}
};
</script>
Login.vue
<template>
<div>
<h1>登录</h1>
<button @click="login">登录</button>
</div>
</template>
<script>
import { useRouter } from 'vue-router';
export default {
setup() {
const router = useRouter();
const login = () => {
localStorage.setItem('token', 'dummy-token');
router.push('/');
};
return { login };
}
};
</script>
- 效果:
- 未登录访问
/product/1跳转到/login。 - 登录后可访问产品页面,退出后重定向。
- 未登录访问
应用场景
- 动态路由:
- 用户详情、文章页面(如
/post/:id)。
- 用户详情、文章页面(如
- 路由守卫:
- 权限控制、数据预加载(如导航前加载用户信息)。
- 404 处理:
- 通配符路由捕获无效路径。
注意事项
- 参数变化:
- 动态参数变化时组件不会重新渲染,需监听
route:watch(() => route.params.id, (newId) => { // 更新数据 });
- 动态参数变化时组件不会重新渲染,需监听
- 守卫顺序:
- 全局 -> 路由独享 -> 组件内,按序执行。
- 性能:
- 避免复杂守卫逻辑影响导航速度。
总结
动态路由通过参数实现灵活页面渲染,路由守卫则提供了强大的导航控制能力。结合 Vue Router 4 的 API,你可以轻松实现动态导航和权限管理。本节的实践为你打下了坚实基础,下一节将探讨嵌套路由与懒加载,进一步丰富你的路由技能!
