基于Vue.js网页开发相关知识:Vue-router
一、基础知识
vue-router
是 Vue.js 官方的路由管理器,用于实现单页面应用(SPA)的路由功能。以下从几个方面对 vue-router
进行详细分析:
1. 核心概念
路由配置
vue-router
通过定义路由配置对象来管理应用的路由。每个路由配置对象包含路径(path
)、组件(component
)等信息。
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';
import About from './views/About.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
];
const router = new VueRouter({
routes
});
export default router;
路由导航
在 Vue 应用中,可以通过
<router-link>
组件实现声明式导航,也可以使用编程式导航方法,如router.push
、router.replace
等。
<template>
<div>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<button @click="goToAbout">Go to About</button>
</div>
</template>
<script>
export default {
methods: {
goToAbout() {
this.$router.push('/about');
}
}
};
</script>
路由钩子
vue-router
提供了多种路由钩子,用于在路由导航过程中执行特定的逻辑,如全局前置守卫、路由独享守卫、组件内守卫等。
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 可以在这里进行权限验证等操作
if (to.meta.requiresAuth &&!isAuthenticated()) {
next('/login');
} else {
next();
}
});
2. 工作原理
监听 URL 变化
vue-router
通过监听浏览器的hashchange
或popstate
事件来检测 URL 的变化。当 URL 发生变化时,会根据当前的 URL 匹配相应的路由配置。渲染组件
一旦匹配到合适的路由,
vue-router
会根据路由配置中的组件信息,动态地将对应的组件渲染到<router-view>
组件所在的位置。
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
3. 路由模式
hash 模式
- 原理:URL 中使用
#
符号来分隔路径,如http://example.com/#/home
。当#
后面的路径发生变化时,不会向服务器发送请求,而是触发hashchange
事件。- 优点:兼容性好,不需要服务器端的特殊配置。
- 缺点:URL 中带有
#
符号,不够美观。history 模式
- 原理:使用 HTML5 的
History API
来管理路由,URL 看起来更像传统的 URL,如http://example.com/home
。当用户点击链接或使用浏览器的前进、后退按钮时,会调用pushState
或replaceState
方法改变 URL,同时触发popstate
事件。- 优点:URL 更美观,没有
#
符号。- 缺点:需要服务器端的支持,当用户直接访问某个路径时,服务器需要返回应用的入口页面(通常是
index.html
)。
4. 路由传参
路径参数
通过在路由配置中定义路径参数,如
:id
,可以在 URL 中传递参数。
const routes = [
{
path: '/user/:id',
name: 'User',
component: () => import('./views/User.vue')
}
];
在组件中可以通过 $route.params
来获取参数:
<template>
<div>
<p>User ID: {{ $route.params.id }}</p>
</div>
</template>
查询参数
使用查询参数可以在 URL 中传递额外的信息,如
?key=value
。
this.$router.push({ path: '/search', query: { keyword: 'vue' } });
在组件中可以通过 $route.query
来获取查询参数:
<template>
<div>
<p>Search Keyword: {{ $route.query.keyword }}</p>
</div>
</template>
5. 路由懒加载
为了提高应用的性能,可以使用路由懒加载来实现组件的按需加载。
const routes = [
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
];
6. 嵌套路由
vue-router
支持嵌套路由,即一个路由中可以包含子路由。
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
];
总结
vue-router
是 Vue.js 应用中不可或缺的一部分,它提供了强大的路由管理功能,包括路由配置、导航、钩子、传参、懒加载、嵌套路由等。通过合理使用这些功能,可以构建出高效、灵活的单页面应用。同时,不同的路由模式和传参方式也能满足各种不同的业务需求。
二、Vue Router传递和接收参数详细
在 Vue Router 里,传递和接收参数有多种途径,下面详细介绍几种常见的方法。
1. 路径参数(Path Parameters)
传递参数
在路由配置里定义路径参数,用冒号
:
来标记参数。例如:
const routes = [
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue')
}
];
在这个例子中,/user/:id
里的 :id
就是路径参数。在导航时,你可以这样传递参数:
<template>
<div>
<router-link :to="`/user/${userId}`">Go to User Page</router-link>
<button @click="goToUserPage">Go to User Page</button>
</div>
</template>
<script>
export default {
data() {
return {
userId: 1
};
},
methods: {
goToUserPage() {
this.$router.push(`/user/${this.userId}`);
}
}
};
</script>
接收参数
在目标组件里,能够借助
$route.params
来获取路径参数。示例如下:
<template>
<div>
<h1>User ID: {{ $route.params.id }}</h1>
</div>
</template>
<script>
export default {
mounted() {
console.log('User ID:', this.$route.params.id);
}
};
</script>
2. 查询参数(Query Parameters)
传递参数
查询参数以键值对的形式添加到 URL 的查询字符串中,使用
?
来分隔路径和查询字符串,多个参数间用&
分隔。你可以通过以下两种方式传递查询参数:
<template>
<div>
<router-link :to="{ path: '/search', query: { keyword: 'vue' } }">Search Vue</router-link>
<button @click="search">Search Vue</button>
</div>
</template>
<script>
export default {
methods: {
search() {
this.$router.push({ path: '/search', query: { keyword: 'vue' } });
}
}
};
</script>
接收参数
在目标组件中,使用
$route.query
来获取查询参数:
<template>
<div>
<h1>Search Keyword: {{ $route.query.keyword }}</h1>
</div>
</template>
<script>
export default {
mounted() {
console.log('Search Keyword:', this.$route.query.keyword);
}
};
</script>
3. 命名路由传参
传递参数
使用命名路由时,可通过
params
或query
传递参数:
<template>
<div>
<router-link :to="{ name: 'User', params: { id: userId } }">Go to User Page</router-link>
<button @click="goToUserPage">Go to User Page</button>
</div>
</template>
<script>
export default {
data() {
return {
userId: 1
};
},
methods: {
goToUserPage() {
this.$router.push({ name: 'User', params: { id: this.userId } });
}
}
};
</script>
接收参数
接收方式和路径参数、查询参数相同:
<template>
<div>
<h1>User ID: {{ $route.params.id }}</h1>
</div>
</template>
<script>
export default {
mounted() {
console.log('User ID:', this.$route.params.id);
}
};
</script>
4. 路由元信息(Meta Fields)传参
传递参数
在路由配置里可以添加
meta
字段来传递额外信息:const routes = [ { path: '/dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } } ];
接收参数
在组件中,通过
$route.meta
来获取元信息:<template> <div> <h1>Dashboard</h1> </div> </template> <script> export default { mounted() { console.log('Requires Auth:', this.$route.meta.requiresAuth); } }; </script>
5. 动态路由配置
传递参数
在创建路由实例时,可以通过动态路由配置传递参数。不过这种方式一般用于全局配置或者插件中:
const router = new VueRouter({ routes: [ { path: '/', component: Home, props: { message: 'Welcome to the home page' } } ] });
接收参数
在组件里,使用
props
选项接收参数:<template> <div> <h1>{{ message }}</h1> </div> </template> <script> export default { props: ['message'] }; </script>
三、Vue Router 导航守卫
在 Vue Router 中,导航守卫主要用来对路由进行权限控制、状态管理、数据预加载等操作,确保在路由切换时能够执行特定的逻辑。
概念
导航守卫可以理解为路由切换过程中的拦截器,它会在路由切换的不同阶段被触发,允许你在这些阶段执行自定义的逻辑,比如验证用户是否登录、判断用户是否有权限访问某个页面等。Vue Router 提供了多种类型的导航守卫,包括全局守卫、路由独享守卫和组件内守卫。
用法
1. 全局守卫
全局守卫会在每次路由切换时都被触发,包括全局前置守卫、全局解析守卫和全局后置钩子。
全局前置守卫 (
router.beforeEach
)全局前置守卫会在路由切换开始前被触发,你可以在这里进行权限验证、数据预加载等操作。
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';
import Dashboard from './views/Dashboard.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true } // 表示该路由需要登录才能访问
}
];
const router = new VueRouter({
routes
});
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to: 即将要进入的目标路由对象
// from: 当前导航正要离开的路由对象
// next: 用于控制路由跳转的函数
if (to.meta.requiresAuth &&!isAuthenticated()) {
// 如果目标路由需要登录且用户未登录
next('/login'); // 跳转到登录页面
} else {
next(); // 继续路由跳转
}
});
function isAuthenticated() {
// 这里可以实现具体的登录验证逻辑
return localStorage.getItem('token')!== null;
}
export default router;
全局解析守卫 (
router.beforeResolve
)全局解析守卫和全局前置守卫类似,但它在导航被确认之前,所有组件内守卫和异步路由组件被解析之后触发。
router.beforeResolve((to, from, next) => {
// 可以在这里进行一些最后的数据验证或处理
next();
});
全局后置钩子 (
router.afterEach
)全局后置钩子在路由切换完成后被触发,它没有
next
函数,通常用于一些日志记录、页面滚动等操作。
router.afterEach((to, from) => {
// 可以在这里记录页面访问日志
console.log(`从 ${from.name} 页面跳转到 ${to.name} 页面`);
});
2. 路由独享守卫
路由独享守卫是针对某个具体路由设置的守卫,只会在该路由切换时被触发。
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
// 这里可以实现该路由的特定权限验证逻辑
if (hasDashboardAccess()) {
next();
} else {
next('/');
}
}
}
];
function hasDashboardAccess() {
// 实现具体的权限验证逻辑
return true;
}
3. 组件内守卫
组件内守卫是在组件内部定义的守卫,用于控制该组件路由切换时的行为。
<template>
<div>
<h1>User Profile</h1>
</div>
</template>
<script>
export default {
// 路由进入前守卫
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能访问组件实例 `this`,因为当守卫执行前,组件实例还没被创建
next(vm => {
// 通过 `vm` 访问组件实例
// 可以在这里进行一些初始化操作
});
},
// 路由更新时守卫
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 可以访问组件实例 `this`
next();
},
// 路由离开时守卫
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
const answer = window.confirm('确定要离开吗?');
if (answer) {
next();
} else {
next(false);
}
}
};
</script>
总结
导航守卫为 Vue Router 提供了强大的路由控制能力,通过合理使用不同类型的导航守卫,可以确保应用的安全性和用户体验。在实际开发中,你可以根据具体需求选择合适的导航守卫来实现相应的功能。
四、全局解析守卫和全局后置钩子有什么区别
在 Vue Router 里,全局解析守卫(router.beforeResolve
)和全局后置钩子(router.afterEach
)都是用于处理路由导航过程中的逻辑,但它们在触发时机、功能用途和参数方面存在显著区别,下面为你详细介绍:
触发时机
- 全局解析守卫(
router.beforeResolve
):在导航被确认之前,且所有组件内守卫和异步路由组件被解析之后触发。这意味着在这个阶段,路由跳转的目标组件已经确定,相关的异步组件也已经加载完成,但路由还未正式跳转。例如,当你使用路由懒加载时,对应的异步组件加载完成后,全局解析守卫就会被触发。- 全局后置钩子(
router.afterEach
):在路由跳转完成之后触发,此时新的路由已经完全渲染到页面上,用户可以看到新的页面内容。
功能用途
- 全局解析守卫(
router.beforeResolve
):由于它在路由跳转前触发,因此可以用于进行一些最后的数据验证或处理。比如,你可以在这个守卫中检查目标路由所需的数据是否已经正确加载,如果数据不完整,可以进行数据的预加载或者跳转到其他合适的页面。router.beforeResolve((to, from, next) => { // 假设需要检查目标路由是否有特定的数据 if (to.meta.requiresData &&!isDataLoaded()) { // 数据未加载,进行数据加载操作 loadData().then(() => { next(); }).catch(() => { // 数据加载失败,跳转到错误页面 next('/error'); }); } else { next(); } }); function isDataLoaded() { // 实现数据加载检查逻辑 return false; } function loadData() { // 实现数据加载逻辑 return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }); }
- 全局后置钩子(
router.afterEach
):通常用于执行一些与路由跳转完成后相关的操作,如页面滚动到顶部、记录页面访问日志、分析用户行为等。因为此时页面已经渲染完成,适合进行一些与页面展示相关的操作。router.afterEach((to, from) => { // 页面滚动到顶部 window.scrollTo(0, 0); // 记录页面访问日志 console.log(`从 ${from.name} 页面跳转到 ${to.name} 页面`); });
参数与控制能力
- 全局解析守卫(
router.beforeResolve
):和全局前置守卫一样,接收to
、from
和next
三个参数。to
表示即将要进入的目标路由对象,from
表示当前导航正要离开的路由对象,next
是一个函数,用于控制路由的跳转行为,可以通过调用next()
继续路由跳转,也可以通过next('/path')
跳转到其他路径。- 全局后置钩子(
router.afterEach
):只接收to
和from
两个参数,不接收next
函数。这意味着它不能控制路由的跳转,只能在路由跳转完成后执行一些操作。
总结:
综上所述,全局解析守卫侧重于在路由跳转前进行最后的数据验证和处理,而全局后置钩子则侧重于在路由跳转完成后执行一些与页面展示和用户行为记录相关的操作。
五、全局解析守卫的最佳实践
全局解析守卫(router.beforeResolve
)在导航被确认之前,所有组件内守卫和异步路由组件被解析之后触发。下面为你介绍一些全局解析守卫的最佳实践。
1. 数据预加载和验证
在路由跳转前,确保目标路由所需的数据已经加载完成,避免在组件渲染时出现数据缺失的情况。
import router from './router';
// 模拟数据加载函数
function loadData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ message: 'Data loaded' });
}, 1000);
});
}
router.beforeResolve(async (to, from, next) => {
if (to.meta.requiresData) {
try {
// 加载数据
const data = await loadData();
// 将数据存储到路由的 meta 字段中,方便在组件中使用
to.meta.data = data;
next();
} catch (error) {
// 数据加载失败,跳转到错误页面
next('/error');
}
} else {
next();
}
});
在上述代码中,我们定义了一个 loadData
函数用于模拟数据加载过程。在全局解析守卫中,检查目标路由的 meta
字段是否包含 requiresData
,如果包含,则调用 loadData
函数加载数据。加载成功后,将数据存储到 to.meta.data
中,方便在组件中使用;如果加载失败,则跳转到错误页面。
2. 权限二次验证
在全局前置守卫中进行了初步的权限验证后,在全局解析守卫中可以进行更细致的权限检查,确保用户确实有权限访问目标路由。
import router from './router';
// 模拟检查用户是否有权限访问某个路由
function hasPermission(to) {
const userRole = localStorage.getItem('userRole');
if (to.meta.requiredRole) {
return userRole === to.meta.requiredRole;
}
return true;
}
router.beforeResolve((to, from, next) => {
if (hasPermission(to)) {
next();
} else {
// 没有权限,跳转到无权限页面
next('/no-permission');
}
});
这里定义了一个 hasPermission
函数,用于检查用户是否有权限访问目标路由。在全局解析守卫中调用该函数进行权限验证,如果有权限则继续路由跳转,否则跳转到无权限页面。
3. 路由过渡动画控制
在路由切换时,可以根据不同的路由配置控制过渡动画的显示和隐藏。
import router from './router';
import { setTransition } from './transitionUtils';
router.beforeResolve((to, from, next) => {
if (to.meta.transition) {
// 设置过渡动画
setTransition(to.meta.transition);
} else {
// 移除过渡动画
setTransition(null);
}
next();
});
假设存在一个 transitionUtils
模块,其中包含 setTransition
函数用于设置过渡动画。在全局解析守卫中,检查目标路由的 meta
字段是否包含 transition
,如果包含,则调用 setTransition
函数设置相应的过渡动画;否则,移除过渡动画。
4. 性能优化和资源释放
在路由切换前,可以释放上一个路由占用的资源,避免内存泄漏。
import router from './router';
import { releaseResources } from './resourceUtils';
router.beforeResolve((to, from, next) => {
if (from.meta.requiresResources) {
// 释放上一个路由占用的资源
releaseResources();
}
next();
});
假设存在一个 resourceUtils
模块,其中包含 releaseResources
函数用于释放资源。在全局解析守卫中,检查上一个路由的 meta
字段是否包含 requiresResources
,如果包含,则调用 releaseResources
函数释放资源。
六、全局解析守卫和全局前置守卫的区别
全局解析守卫(router.beforeResolve
)和全局前置守卫(router.beforeEach
)都是 Vue Router 中用于控制路由导航的全局钩子函数,但它们在触发时机、功能侧重点以及对异步组件的处理上存在明显区别,以下是详细介绍:
触发时机
- 全局前置守卫(
router.beforeEach
):是路由导航过程中最早触发的全局守卫,在路由跳转开始时,当用户点击链接或者调用路由跳转方法(如this.$router.push
)后,立即触发该守卫。此时,路由跳转的目标路由和当前路由信息已经确定,但还未开始解析异步路由组件(如果有的话)。- 全局解析守卫(
router.beforeResolve
):在导航被确认之前,所有组件内守卫(如beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
)和异步路由组件都已经被解析完成之后触发。也就是说,在这个阶段,目标路由对应的组件已经准备好进行渲染,只是还未正式完成路由跳转。功能侧重点
- 全局前置守卫(
router.beforeEach
):主要用于进行一些通用的、基础的路由验证和权限控制,例如判断用户是否登录、是否有权限访问某个路由等。由于它触发较早,可以在路由跳转的最初阶段就对不符合条件的导航进行拦截,避免不必要的组件加载和资源浪费。
// 全局前置守卫示例:检查用户是否登录
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token');
if (to.meta.requiresAuth &&!isLoggedIn) {
next('/login');
} else {
next();
}
});
- 全局解析守卫(
router.beforeResolve
):更侧重于在路由跳转前进行最后的数据验证、处理和准备工作。因为此时异步组件已经加载完成,所以可以对组件所需的数据进行最后的检查和处理,确保组件渲染时数据的完整性。
// 全局解析守卫示例:检查目标路由所需的数据是否加载完成
router.beforeResolve((to, from, next) => {
if (to.meta.requiresData &&!isDataLoaded()) {
// 数据未加载,进行数据加载操作
loadData().then(() => {
next();
}).catch(() => {
// 数据加载失败,跳转到错误页面
next('/error');
});
} else {
next();
}
});
function isDataLoaded() {
// 实现数据加载检查逻辑
return false;
}
function loadData() {
// 实现数据加载逻辑
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}
对异步组件的处理
- 全局前置守卫(
router.beforeEach
):在异步路由组件还未开始解析时就会触发,因此在这个守卫中无法获取到异步组件的具体信息,也不能对异步组件进行相关的操作。- 全局解析守卫(
router.beforeResolve
):在异步路由组件解析完成之后触发,所以可以获取到异步组件的详细信息,并且可以基于这些信息进行一些额外的处理,比如根据异步组件的状态来决定是否继续路由跳转。
综上所述,全局前置守卫和全局解析守卫在路由导航过程中扮演着不同的角色,通过合理使用这两个守卫,可以实现更细致、更完善的路由控制和数据处理。
七、Vue - Router 的最佳实践:
路由配置
- 合理组织路由结构:按照功能模块划分路由,将相关的路由放在一起,保持路由配置的清晰和易于维护。例如,将用户相关的路由放在
user
模块下,将文章相关的路由放在article
模块下。- 使用命名路由和命名视图:为路由和视图指定名称,方便在代码中进行引用和导航。命名路由可以通过名称进行跳转,避免硬编码路径;命名视图可以在同一个路由中同时显示多个视图组件。
- 设置路由元信息:在路由配置中使用
meta
字段来存储路由相关的元信息,如页面标题、是否需要登录、权限要求等。这些元信息可以在路由守卫或组件中进行访问和处理。路由守卫
- 全局前置守卫进行权限验证:在全局前置守卫中检查用户的登录状态和权限,根据用户的角色和权限来决定是否允许访问目标路由。如果用户没有权限访问,可将其重定向到登录页面或无权限提示页面。
- 组件内守卫进行数据检查:在组件内的守卫(如
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
)中进行组件相关的数据检查和处理。例如,在beforeRouteLeave
中询问用户是否保存未保存的表单数据,避免数据丢失。- 全局解析守卫处理异步数据:在全局解析守卫中处理异步数据加载,确保在路由跳转前所有必要的数据都已加载完成。这样可以避免组件渲染时出现数据缺失的情况,提高用户体验。
路由过渡效果
- 使用 CSS 过渡类:通过在路由组件上添加 CSS 过渡类,实现路由切换时的过渡效果。可以使用 Vue - Router 提供的
transition
组件结合 CSS 样式来定义不同的过渡效果,如淡入淡出、滑动等。- 结合动画库:对于更复杂的动画效果,可以结合第三方动画库(如 GSAP、Anime.js 等)来实现。在路由守卫或组件的生命周期钩子中触发动画,根据路由的变化来执行相应的动画操作。
路由懒加载
- 按需加载路由组件:使用路由懒加载可以将路由组件的加载延迟到实际需要访问该路由时,从而减少初始加载时间,提高页面的加载速度。在路由配置中,通过将组件定义为异步函数来实现懒加载。
const Home = () => import('./views/Home.vue'); const About = () => import('./views/About.vue'); const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ];
导航处理
- 避免重复导航错误:在进行路由跳转时,要注意避免重复导航导致的错误。可以通过在路由守卫中进行判断,或者使用
router.replace
方法来替换当前路由,而不是使用router.push
方法多次添加相同的路由。- 处理浏览器前进和后退:根据应用的需求,合理处理浏览器的前进和后退操作。可以使用路由的
history
对象来监听浏览器的导航事件,并在必要时进行相应的处理,如恢复页面状态、重新加载数据等。路由与组件通信
- 通过路由参数传递数据:在路由跳转时,可以通过路由参数将数据传递给目标组件。可以使用
params
或query
参数来传递简单的数据,对于复杂的数据,可以将其序列化为 JSON 字符串进行传递。- 使用 Vuex 或全局状态管理:对于需要在多个路由组件之间共享的数据,建议使用 Vuex 或其他全局状态管理工具来管理。这样可以方便地在不同组件之间进行数据的获取、更新和共享,提高数据的可维护性和一致性。