Vue Router是一个官方的路由管理器,它可以让我们在Vue应用中实现单页面应用(SPA)的效果,即通过改变URL而不刷新页面来显示不同的内容。Vue Router可以让我们定义多个路由,每个路由对应一个组件,当URL匹配到某个路由时,就会渲染对应的组件。Vue Router还提供了很多高级功能,如嵌套路由、动态路由、命名路由、导航守卫、路由元信息等,让我们可以更灵活地控制路由的行为和状态。
在本文中,我们将介绍Vue Router
的基本使用方法,以及一些常用的API
和技巧。我们假设你已经有了一些Vue
的基础知识,如果不熟悉Vue
,请先阅读官方文档。本文使用的Vue Router
版本是4.x
,这是目前最新的版本,它支持Vue 3.x
,并且有一些重要的变化和改进。如果你还在使用旧版本,请参考旧版文档,做好兼容处理。
公众号:Code程序人生,个人网站:https://creatorblog.cn
安装和引入
要使用Vue Router
,首先需要安装它。你可以通过npm
或yarn
来安装:
# npm
npm install vue-router
# yarn
yarn add vue-router
然后,在你的入口文件(通常是main.js
)中引入并创建一个路由实例:
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
// 定义路由配置,每个路由映射一个组件
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
// ...其他路由
]
// 创建一个路由实例,使用history模式
const router = createRouter({
history: createWebHistory(),
routes, // 等价于routes: routes
})
// 创建一个Vue应用实例,并挂载到#app元素上
const app = createApp(App)
// 将路由实例注入到应用实例中
app.use(router)
// 挂载应用
app.mount('#app')
这里有几点需要注意:
-
Vue Router 4.x
使用了函数式的API
来创建路由实例,而不是像3.x
那样使用new VueRouter()
构造函数。这样可以避免全局污染和依赖注入的问题。 -
Vue Router 4.x
支持三种模式:history
、hash
和memory
。其中history
模式是推荐的模式,它使用HTML5 History API
来实现无刷新的URL
切换。要使用history
模式,需要调用createWebHistory()
函数来创建一个history
对象,并传递给createRouter()
函数。如果你想使用hash
模式或memory
模式,请分别调用createWebHashHistory()
或createWebMemoryHistory()
函数。 - 路由配置是一个数组,每个元素是一个对象,表示一个路由。每个路由对象至少需要有两个属性:
path
和component
。path
是一个字符串,表示URL
路径;component
是一个组件,表示要渲染的内容。你可以在routes
数组中定义任意多个路由对象。 - 要将路由实例注入到应用实例中,需要调用
app.use(router)
方法。这样就可以在应用中使用路由相关的功能了。
使用router-link和router-view
有了路由实例和配置之后,我们就可以在应用中使用router-link
和router-view
两个组件了。router-link
是一个特殊的组件,它会渲染成一个a
标签,并且可以自动添加或移除active
类名来表示当前激活的路由。router-link
接受一个to
属性,表示要跳转的目标路径。例如:
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
这会渲染成:
<a href="/" class="router-link-active">Home</a>
<a href="/about">About</a>
当URL
变化时,router-link
会自动更新active
类名,以及对应的样式。你可以通过active-class
属性来自定义active
类名,或者通过exact
属性来控制是否需要完全匹配路径。
router-view
是另一个特殊的组件,它会根据当前匹配的路由来渲染对应的组件。你可以把它放在任何地方,通常是放在App
组件的模板中,作为一个占位符。例如:
<template>
<div id="app">
<h1>Vue Router Demo</h1>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<!-- 这里会渲染匹配到的组件 -->
<router-view></router-view>
</div>
</template>
当URL
变化时,router-view
会自动更新渲染的组件,以及对应的生命周期钩子。你可以通过name
属性来给router-view
命名,以便在嵌套路由中使用。
使用动态路由和命名路由
有时候,我们需要根据URL
中的参数来渲染不同的组件,例如用户的个人主页或者文章的详情页。这时候,我们可以使用动态路由来实现。动态路由是指在path
中使用冒号(:)来表示一个参数,例如:
const routes = [
// ...其他路由
// 动态路由,匹配/user/任意值
{ path: '/user/:id', component: User },
// 动态路由,匹配/post/任意值
{ path: '/post/:slug', component: Post },
]
这样,当URL
为/user/123
或/post/hello-world
时,就会匹配到对应的路由,并渲染User
或Post
组件。在组件中,我们可以通过$route.params
对象来获取动态路由参数的值,例如:
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
</div>
</template>
或者:
<template>
<div class="post">
<h2>Post {{ $route.params.slug }}</h2>
</div>
</template>
当然,我们也可以给动态路由参数添加一些限制,例如正则表达式。这样可以避免匹配到不合法的值。例如:
const routes = [
// ...其他路由
// 动态路由,只匹配/user/数字
{ path: '/user/:id(\\d+)', component: User },
]
这样,当URL
为/user/abc
时,就不会匹配到该路由了。
除了使用path
来定义路由,我们还可以使用name
来给路由命名。命名路由可以让我们在跳转或链接时更方便地引用路由,而不用担心path
的变化。例如:
const routes = [
// ...其他路由
// 命名路由
{ path: '/user/:id', name: 'user', component: User },
]
这样,在router-link
中,我们就可以使用name
属性来指定目标路由,并且通过params
属性来传递动态路由参数。例如:
<router-link :to="{ name: 'user', params: { id: 123 }}">User 123</router-link>
这会渲染成:
<a href="/user/123">User 123</a>
同样,在编程式导航中,我们也可以使用name
和params
来跳转到目标路由。例如:
// 在组件中
this.$router.push({ name: ‘user’, params: { id: 456 }})
// 这会跳转到 /user/456
使用命名路由的好处是,我们可以在任何地方修改路由的path
,而不用担心影响到其他地方的引用。只要保持路由的name
不变,就可以保证路由的正确性。
使用嵌套路由和命名视图
有时候,我们需要在一个页面中显示多个组件,或者在一个组件中嵌套另一个组件。这时候,我们可以使用嵌套路由和命名视图来实现。嵌套路由是指在一个路由对象中使用children
属性来定义子路由,例如:
const routes = [
// ...其他路由
// 嵌套路由
{
path: '/user/:id',
component: User,
children: [
// 当URL为/user/:id/profile时,渲染UserProfile组件
{ path: 'profile', component: UserProfile },
// 当URL为/user/:id/posts时,渲染UserPosts组件
{ path: 'posts', component: UserPosts },
]
}
]
这样,当URL
为/user/123/profile
或/user/123/posts
时,就会匹配到对应的子路由,并渲染User
组件和UserProfile
或UserPosts
组件。注意,子路由的path
不需要加斜杠(/),因为它是相对于父路由的path
来解析的。
要在父组件中渲染子组件,我们需要在父组件的模板中使用router-view
作为占位符。例如:
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<!-- 这里会渲染子组件 -->
<router-view></router-view>
</div>
</template>
如果我们想要同时显示多个组件,而不是嵌套显示,我们可以使用命名视图来实现。命名视图是指给router-view
添加一个name
属性来区分不同的视图,并且在路由配置中使用components
属性来指定多个组件。例如:
const routes = [
// ...其他路由
// 命名视图
{
path: '/dashboard',
components: {
default: Dashboard,
sidebar: Sidebar,
header: Header,
}
}
]
这样,当URL
为/dashboard
时,就会匹配到该路由,并渲染Dashboard
、Sidebar
和Header
三个组件。注意,components
是一个对象,它的键是router-view
的name
属性,它的值是对应的组件。如果没有指定name
属性,默认为default
。
要在应用中使用命名视图,我们需要在模板中使用多个router-view
并给它们添加name
属性。例如:
<template>
<div id="app">
<!-- 这里会渲染Header组件 -->
<router-view name="header"></router-view>
<div class="main">
<!-- 这里会渲染Sidebar组件 -->
<router-view name="sidebar"></router-view>
<!-- 这里会渲染Dashboard组件 -->
<router-view></router-view>
</div>
</div>
</template>
当然,我们也可以将嵌套路由和命名视图结合起来使用,以实现更复杂的布局和导航。例如:
const routes = [
// ...其他路由
// 嵌套路由和命名视图
{
path: '/user/:id',
component: User,
children: [
{
path: 'profile',
components: {
default: UserProfile,
sidebar: UserSidebar,
}
},
{
path: 'posts',
components: {
default: UserPosts,
sidebar: UserSidebar,
}
},
]
}
]
这样,当URL
为/user/123/profile
或/user/123/posts
时,就会匹配到对应的子路由,并渲染User
、UserProfile
或UserPosts
和UserSidebar
三个组件。注意,子路由的components
也是一个对象,它的键是router-view
的name
属性,它的值是对应的组件。
要在父组件中使用命名视图,我们也需要在模板中使用多个router-view
并给它们添加name
属性。例如:
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<!-- 这里会渲染UserSidebar组件 -->
<router-view name="sidebar"></router-view>
<!-- 这里会渲染UserProfile或UserPosts组件 -->
<router-view></router-view>
</div>
</template>
使用导航守卫和路由元信息
有时候,我们需要在路由跳转时进行一些控制或处理,例如验证用户权限、显示加载提示、记录访问日志等。这时候,我们可以使用导航守卫来实现。导航守卫是一些函数,它们会在路由跳转前后被调用,让我们可以拦截或修改路由的行为。Vue Router
提供了三种类型的导航守卫:全局守卫、路由独享守卫和组件内守卫。
全局守卫是指在路由实例上注册的守卫,它们会在任何路由跳转时被调用。全局守卫有三种:beforeEach
、beforeResolve
和afterEach
。beforeEach
守卫会在路由跳转前被调用,我们可以在这里进行一些权限验证或数据预处理。例如:
// 在创建路由实例之后
router.beforeEach((to, from, next) => {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 检查目标路由是否需要登录
if (to.meta.requiresAuth) {
// 检查用户是否已经登录
if (store.state.user) {
// 已经登录,放行
next()
} else {
// 没有登录,跳转到登录页
next({ path: '/login' })
}
} else {
// 不需要登录,放行
next()
}
})
这里,我们使用了to.meta
属性来判断目标路由是否需要登录。meta
属性是一个自定义的对象,我们可以在路由配置中给每个路由添加一些元信息,例如:
const routes = [
// ...其他路由
// 需要登录的路由
{ path: '/profile', component: Profile, meta: { requiresAuth: true }},
// 不需要登录的路由
{ path: '/login', component: Login },
]
这样,在beforeEach
守卫中,我们就可以根据meta
属性来进行不同的处理。注意,在beforeEach
守卫中,我们必须调用next
函数来决定是否放行或重定向。next
函数可以接受一个参数,表示要跳转的目标路径或路由对象。如果不传递参数,则表示放行当前的跳转。
beforeResolve
守卫和beforeEach
守卫类似,但是会在所有异步组件被解析后才被调用。我们可以在这里进行一些最后的检查或处理。例如:
// 在创建路由实例之后
router.beforeResolve((to, from, next) => {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 显示加载提示
store.commit('showLoading')
// 放行
next()
})
afterEach
守卫会在路由跳转后被调用,我们可以在这里进行一些收尾工作或清理工作。例如:
// 在创建路由实例之后
router.afterEach((to, from) => {
// to是目标路由对象,from是来源路由对象
// 隐藏加载提示
store.commit(‘hideLoading’);
// 记录访问日志
store.dispatch(‘logVisit’, { to, from })
});
路由独享守卫是指在路由配置中定义的守卫,它们只会在匹配到该路由时被调用。路由独享守卫有两种:beforeEnter
和beforeLeave
。beforeEnter
守卫会在进入该路由前被调用,我们可以在这里进行一些特定的处理或拦截。例如:
const routes = [
// ...其他路由
// 路由独享守卫
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 检查用户是否是管理员
if (store.state.user.role === 'admin') {
// 是管理员,放行
next()
} else {
// 不是管理员,跳转到首页
next({ path: '/' })
}
}
}
]
这里,我们使用了beforeEnter
守卫来判断用户是否有权限访问/admin
路由。注意,在beforeEnter
守卫中,我们也必须调用next
函数来决定是否放行或重定向。
beforeLeave
守卫会在离开该路由前被调用,我们可以在这里进行一些确认或保存工作。例如:
const routes = [
// ...其他路由
// 路由独享守卫
{
path: '/edit/:id',
component: Edit,
beforeLeave: (to, from, next) => {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 检查编辑内容是否已经保存
if (this.$refs.editor.isSaved()) {
// 已经保存,放行
next()
} else {
// 没有保存,弹出确认框
if (window.confirm('您的编辑内容尚未保存,确定要离开吗?')) {
// 确定离开,放行
next()
} else {
// 取消离开,中断跳转
next(false)
}
}
}
}
]
这里,我们使用了beforeLeave
守卫来提示用户是否要保存编辑内容。注意,在beforeLeave
守卫中,我们也必须调用next
函数来决定是否放行或中断。如果传递false
作为参数,则表示中断当前的跳转。
组件内守卫是指在组件选项中定义的守卫,它们只会在该组件被渲染时被调用。组件内守卫有三种:beforeRouteEnter
、beforeRouteUpdate
和beforeRouteLeave
。beforeRouteEnter
守卫会在渲染该组件前被调用,我们可以在这里进行一些数据获取或处理。例如:
<template>
<div class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
}
},
beforeRouteEnter(to, from, next) {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 获取文章数据
fetch(`/api/posts/${to.params.id}`)
.then(res => res.json())
.then(data => {
// 将数据传递给组件实例
next(vm => vm.post = data)
})
},
}
</script>
这里,我们使用了beforeRouteEnter
守卫来获取文章数据,并将其传递给组件实例。注意,在beforeRouteEnter
守卫中,我们无法访问this
,因为组件实例还没有被创建。所以我们需要通过next
函数的回调来访问组件实例。
beforeRouteUpdate
守卫会在当前路由发生变化时被调用,但是该组件被复用时才会触发。我们可以在这里进行一些数据更新或处理。例如:
<template>
<div class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
}
},
beforeRouteEnter(to, from, next) {
// ...省略
},
beforeRouteUpdate(to, from, next) {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 获取新的文章数据
fetch(`/api/posts/${to.params.id}`)
.then(res => res.json())
.then(data => {
// 更新组件实例的数据
this.post = data
// 放行
next()
})
},
}
</script>
这里,我们使用了beforeRouteUpdate
守卫来获取新的文章数据,并更新组件实例的数据。注意,在beforeRouteUpdate
守卫中,我们可以访问this
,因为组件实例已经被创建并复用。同样,在beforeRouteUpdate
守卫中,我们也必须调用next
函数来决定是否放行或重定向。
beforeRouteLeave
守卫会在离开该组件时被调用,我们可以在这里进行一些确认或保存工作。例如:
<template>
<div class="edit">
<h2>编辑文章</h2>
<textarea ref="editor">{{ post.content }}</textarea>
<button @click="save">保存</button>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
saved: false,
}
},
beforeRouteEnter(to, from, next) {
// ...省略
},
beforeRouteLeave(to, from, next) {
// to是目标路由对象,from是来源路由对象,next是一个函数
// 检查编辑内容是否已经保存
if (this.saved) {
// 已经保存,放行
next()
} else {
// 没有保存,弹出确认框
if (window.confirm('您的编辑内容尚未保存,确定要离开吗?')) {
// 确定离开,放行
next()
} else {
// 取消离开,中断跳转
next(false)
}
}
},
methods: {
save() {
// ...省略
this.saved = true
}
}
}
</script>
这里,我们使用了beforeRouteLeave
守卫来提示用户是否要保存编辑内容。注意,在beforeRouteLeave
守卫中,我们也必须调用next
函数来决定是否放行或中断。
使用的注意事项
在使用Vue Router
时,还有一些注意事项需要了解:
- 如果你想使用
HTML5 History
模式,你需要配置你的服务器来支持该模式。具体来说,就是让服务器对于所有的请求都返回index.html
文件,以便让Vue Router
接管路由的处理。你可以参考官方文档中的相关说明来配置你的服务器。 - 如果你想使用
hash
模式,你需要注意URL
中的hash
值可能会影响到锚点的功能。如果你想使用锚点来定位到页面中的某个元素,你需要在路由配置中使用scrollBehavior
属性来自定义滚动行为。你可以参考官方文档中的相关示例来实现你的需求。 - 如果你想使用
memory
模式,你需要注意该模式不会改变URL
,也不会记录历史记录。这意味着用户无法通过浏览器的前进或后退按钮来导航路由,也无法通过刷新页面来保持当前状态。该模式适合一些不需要URL
和历史记录的场景,例如移动端应用或小程序。 - 如果你想在组件中访问路由相关的信息或功能,你可以通过
this.$route
和this.$router
两个属性来实现。 -
this.$route
是一个只读的对象,它包含了当前激活的路由的信息,例如path
、params
、query
、meta
等。你可以通过this.$route
来获取或监视路由的变化,以便进行一些响应式的操作。
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<!-- 根据路由的query参数来显示不同的内容 -->
<div v-if="$route.query.active">
<p>This user is active.</p>
</div>
<div v-else>
<p>This user is inactive.</p>
</div>
</div>
</template>
-
this.$router
是一个可读写的对象,它是路由实例的引用,它包含了一些方法和属性,让我们可以在组件中控制路由的跳转或状态。
<template>
<div class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<!-- 使用router-link来跳转到上一篇或下一篇文章 -->
<nav>
<router-link :to="{ name: 'post', params: { id: prevId }}">Prev</router-link>
<router-link :to="{ name: 'post', params: { id: nextId }}">Next</router-link>
</nav>
<!-- 使用router.go来后退或前进 -->
<button @click="$router.go(-1)">Back</button>
<button @click="$router.go(1)">Forward</button>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
prevId: null,
nextId: null,
}
},
beforeRouteEnter(to, from, next) {
// ...省略
},
beforeRouteUpdate(to, from, next) {
// ...省略
},
methods: {
// 使用router.push来跳转到编辑页
edit() {
this.$router.push({ name: 'edit', params: { id: this.post.id }})
}
}
}
</script>
总结
以上就是Vue Router
的基本使用方法,以及一些常用的API
和技巧。Vue Router
是一个非常强大和灵活的路由管理器,它可以让我们在Vue
应用中实现各种复杂和高效的导航功能。