Vue Router
是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
本章只做学习记录,详尽的内容一定要去官网查看 📚api文档: Vue Router-Vue.js 的官方路由
1. 路由的基本使用
1.1 安装vue-router
npm install vue-router@4
1.2 使用vue-router
📍1、src下创建一个叫 router 的文件夹,并创建 index.js,创建并暴露一个路由器 router
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
// 1. 定义一些路由
// 每个路由都需要映射到一个组件。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 2. 创建路由实例并传递 `routes` 配置
const router = createRouter({
// 2. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
export default router
📍2、创建需要的组件
📍3、在 main.js 中导入并 use router
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
//创建实例,使用 router,并挂载根实例
createApp(App).use(router).mount('#app')
📍4、App.vue 中使用路由
<template>
<h1>Hello Vue-Route!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link> |
<router-link to="/about">Go to About</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
components: {}
}
</script>
<style>
...
}
</style>
router-view
将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。
📍5、测试
点击 “Go to Home”,显示“this is home”
点击 “Go to About”,显示“this is about”
2. 动态路由匹配
很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如:/user/12
和 /user/13
映射到同一个组件。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数
。
📍1、App.vue 使用路由
<template>
<h1>Hello Vue-Route!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link> |
<router-link to="/about">Go to About</router-link> |
<router-link to="/user/12"> 12号用户 </router-link> |
<router-link to="/user/13"> 13号用户 </router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
...
📍2、router/index.js 新增动态路由映射
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import User from '@/components/User.vue'
// 1. 定义一些路由
// 每个路由都需要映射到一个组件。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
]
...
路径参数
用冒号:
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params
的形式暴露出来。因此,我们可以通过更新 User 的模板来呈现当前的用户 ID:
📍3、User 组件
<template>
My id is {{ $route.params.id }}
</template>
📍4、测试
点击 “12号用户”,显示“My id is 12”
点击 “13号用户”,显示“My id is 13”
⚠️注: 你可以在同一个路由中设置有多个路径参数
,它们会映射到 $route.params
上的相应字段。例如:
匹配模式 | 匹配路径 | $route.params |
---|---|---|
/users/:username | /users/eduardo | { username: ‘eduardo’ } |
/users/:username/posts/:postId | /users/eduardo/posts/123 | { username: ‘eduardo’, postId: ‘123’ } |
除了 $route.params
之外,$route
对象还公开了其他有用的信息,如 $route.query
(如果 URL 中存在参数)、$route.hash
等。
3. 路由的匹配语法
3.1 参数中自定义正则
当定义像 :userId
这样的参数时,我们内部可以使用正则表达进行修饰。
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)', component: component1},
// /:productName -> 匹配其他任何内容
{ path: '/:productName', component: component2},
]
现在,转到 /25
将匹配 /:orderId
,其他情况将会匹配 /:productName
。routes 数组的顺序并不重要!
3.2 参数数组
如果你需要匹配具有多个部分的路由,如 /first/second/third
,你应该用 *
(0 个或多个)和 +
(1 个或多个)将参数标记为可重复:
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这将为你提供一个参数数组
,而不是一个字符串,并且在使用命名路由时也需要你传递一个数组:
📍1、router/index.js 新增动态路由映射:
const routes = [
...
{ path: '/user/:id*', component: User }
]
📍2、App.vue 使用路由
<router-link to="/user/11/22/33"> 参数数组 </router-link>
📍3、测试
点击 “参数数组”,显示“My id is [ “11”, “22”, “33” ]”
4. 嵌套路由
一些应用程序的 UI 由多层嵌套的组件组成。例如:/user/johnny/profile
、/user/johnny/posts
中,profile、posts 属于 johnny 下属 url,可以配置嵌套路由
来表达这种关系。
我们上述案例中的 <router-view>
是一个顶层的 router-view
。它渲染顶层路由匹配的组件。同样地,一个被渲染的组件也可以包含自己嵌套的 <router-view>
。例如,如果我们在 User
组件的模板内添加一个 <router-view>
。
📍1、User 组件添加<router-view>
<template>
My id is {{ $route.params.id }}
<p>
<router-link to="/user/12/profile">头像</router-link> |
<router-link to="/user/12/posts">岗位</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
📍2、router/index.js 新增子路由映射
要将组件渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children
:
...
import User from '@/components/user'
import UserProfile from '@/components/user/UserProfile.vue'
import UserPosts from '@/components/user/UserPosts.vue'
// 1. 定义一些路由
// 每个路由都需要映射到一个组件。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
]
}
]
...
children
配置只是另一个路由数组
,就像 routes
本身一样。因此,你可以根据自己的需要,不断地嵌套视图。
📍3、子组件
📍4、测试
点击 “头像”
点击 “岗位”
⚠️注意1:⚡如果直接访问子路由,对应的父路由组件也会随之一起加载。
比如,直接访问 http://localhost:8080/user/12/profile
,父组件也会随之加载。
⚠️注意2:⚡子路由 path
可以是相对路径,比如上例中的 path: 'profile'
;也可以是绝对路径:path: '/user/:id/profile'
,推荐 相对路径。
嵌套路由传参
上述案例有个问题,User 组件的访问路径是写死的 <router-link to="/user/12/profile">头像</router-link>
,我们可以通过 v-bind
动态获取上层的路径参数。
<template>
My id is {{ $route.params.id }}
<p>
<router-link :to=" '/user/' + $route.params.id + '/profile' ">头像</router-link> |
<router-link :to=" '/user/' + $route.params.id + '/posts' ">岗位</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
5. 路由命名
除了 path
之外,你还可以为任何路由提供 name
。
📍router/index.js :
const routes = [
{
path: '/', name: 'home', component: Home
},
{
path: '/about', name: 'about', component: About
},
{
path: '/user/:id',
name: 'user',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
]
}
]
为路由路径命名。
📍App.vue:
<template>
<h1>Hello Vue-Route!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<!-- <router-link to="/">Go to Home</router-link> | -->
<router-link :to="{name: 'home'}">Go to Home</router-link> |
<router-link :to="{name: 'about'}">Go to About</router-link> |
<router-link :to="{name: 'user', params: {id: 12}}"> 12号用户 </router-link> |
<router-link :to="{name: 'user', params: {id: 13}}"> 13号用户 </router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
要链接到一个命名的路由,可以向 router-link
组件的 to
属性传递一个对象,路径参数
通过 params
传递。
⚠️注意:⚡此处的 to
属性需要通过 v-bind
修饰。
6. 视图命名
有时候想同时 (同级) 展示多个视图,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。如果 router-view
没有设置名字,那么默认为 default
。
📍App.vue:
<template>
...
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view name="LeftSidebar"></router-view> |
<router-view></router-view> |
<router-view name="RightSidebar"></router-view>
</template>
📍router/index.js :
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
const routes = [
{
path: '/', name: 'home', component: Home
},
{
path: '/about',
name: 'about',
//component: About
components: {
LeftSidebar: UserProfile,
default: About,
RightSidebar: UserPosts
}
},
{
path: '/user/:id',
name: 'user',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
]
}
]
设置 url=/about
的视图 LeftSidebar
的加载组件为 UserProfile
,视图 default
的加载组件为 About
,视图 RightSidebar
的加载组件为 UserPosts
。
📍测试:
点击 “Go to Home”
点击 “Go to About”
7. 编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router
的实例方法,通过编写代码来实现。
在 Vue 实例中,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
。当你点击 <router-link>
时,内部会调用这个方法,所以点击 <router-link :to="...">
相当于调用 router.push(...)
。
声明式 | 编程式 |
---|---|
<router-link :to=“…”> | router.push(…) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
注意:如果提供了 path,params 会被忽略。
router.push
和所有其他导航方法都会返回一个 Promise,让我们可以等到导航完成后才知道是成功还是失败。
📍App.vue:
<template>
<h1>Hello Vue-Route!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<!-- <router-link to="/">Go to Home</router-link> | -->
<router-link :to="{name: 'home'}">Go to Home</router-link> |
<router-link :to="{name: 'about'}">Go to About</router-link> |
<router-link :to="{name: 'user', params: {id: 12}}"> 12号用户 </router-link> |
<router-link :to="{name: 'user', params: {id: 13}}"> 13号用户 </router-link>
<button @click="tiaozhuan">push跳转</button>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
components: {},
methods: {
tiaozhuan(){
this.$router.push({
name: 'user',
params: {
id: 12
}
})
}
}
}
</script>
...
点击 “push跳转”,地址栏跳转到 http://localhost:8080/#/user/12
替换当前位置
它的作用类似于 router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
---|---|
<router-link :to=“…” replace> | router.replace(…) |
8. 重定向和别名
通过 redirect
配置重定向
const routes = [{ path: '/home', component: Component, redirect: '/' }]
//重定向的目标也可以是一个命名的路由
const routes = [{ path: '/home', component: Component, redirect: { name: 'homepage' } }]
通过 alias
配置别名
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
9. 路由组件传参
在第二节提到动态路由匹配,通过 {{ $route.params.id }}
获取路径参数,这样会使得组件和路由紧密耦合,这限制了组件的灵活性。为了组件的灵活性,我们可以通过 props
配置来解耦
//路由 props: true route.params 将被传递给组件的 props
const routes = [{ path: '/user/:id', component: User, props: true }]
//组件 通过 props 指定传递的路径参数
const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ['id'],
template: '<div>User {{ id }}</div>'
}
📍1、router/index.js 路由器配置props:true
📍2、路由映射的组件通过 props 指定参数
📍3、测试
9.1、params传参
params 传参又可分为 声明式
和 编程式
两种方式:
- 声明式 router-link:该方式是通过router-link组件的to属性实现,子路由需要提前配置好参数
<router-link :to="/child/1"> 跳转到子路由 </router-link>
{
path: '/child/:id',
component: Child
}
- 编程式 this.$router.push:同样需要子路由提前配置好参数。
{
path: '/child/:id',
component: Child
}
this.$router.push({
path:'/child/${id}',
})
接收: this.$route.params.id
布尔模式
当 props
设置为 true
时,route.params
将被设置为组件的 props
。
命名视图
对于有命名视图的路由,你必须为每个命名视图定义 props
配置:
const routes = [
{
path: '/user/:id',
components: { default: User, LeftSidebar: UserProfile },
props: { default: true, sidebar: false }
}
]
对象模式
当 props
是静态的时候很有用。
const routes = [
{
path: '/user/:id',
name: 'user',
component: User,
props: { id: 20 },
...
}
]
函数模式
你可以创建一个返回 props
的函数。
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
URL /search?q=vue
将传递 {query: 'vue'}
作为 props
传给 SearchUser 组件。
9.2、query 传参
当需要传递的参数过多,不想放在 URL 时,可以通过参数传参。
下面演示参数、query传参:
📍1、router/index.js 路由器配置 函数模式props
const routes = [
...
{
path: '/query/:id',
name: 'ComponentA',
component: ComponentA,
props: route => ({
id: route.params.id,
title: route.query.title,
userName: route.query.userName
})
}
]
路由组件 定义访问路径
、路由名称
、映射组件
、函数模式返回传递给映射组件的参数集
。
📍2、主组件 App.vue 声明式导航
<template>
<h1>Hello Vue-Route!</h1>
<p>
...
<router-link :to="{name: 'ComponentA', params: { id: '1001' }, query: {title: '路由组件query传参', userName: 'Jack'}}"> query传参 </router-link>
</p>
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</template>
主组件 定义声明式导航,传入 name、params、query。
📍3、新建组件 ComponentA
<template>
this is ComponentA for {{ id }}
<br />
title = {{title}}, userName = {{userName}}
</template>
<script>
export default {
props: ['id', 'title', 'userName']
}
</script>
组件 内通过 props
获取传入的参数。
📍4、测试
点击“query传参”,请求 url 为 http://localhost:8080/query/1001?title=路由组件query传参&userName=Jack
,组件内参数 ‘id’, ‘title’, ‘userName’ 均获取成功。
10. 不同的历史模式
Hash 模式
hash 模式是用 createWebHashHistory()
创建的,它在内部传递的实际 URL 之前使用了一个哈希字符(#
)
HTML5 模式
用 createWebHistory()
创建 HTML5 模式,推荐使用这个模式。需要服务器配置。
11. 导航守卫
相当于拦截器
。vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
全局前置守卫
你可以使用 router.beforeEach
注册一个全局前置守卫
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
to
: 即将要进入的目标from
: 当前导航正要离开的路由返回值
:false:取消当前的导航,true:跳转。
路由独享的守卫
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
组件内的守卫
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
12. 路由元信息
有時候需要添加附加信息到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的 meta
属性来实现,并且它可以在路由地址和导航守卫上都被访问到。
📍1、route/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import About from '@/components/About.vue'
import Home from '@/components/Home.vue'
const routes = [
{
path: '/',
name: 'home',
component: Home,
// 指定路由的 meta 属性
meta: { requiresAuth: false, title: 'home-title' }
},
{
path: '/about',
name: 'about',
component: About,
// 指定路由的 meta 属性
meta: { requiresAuth: false, title: 'about-title' }
}
]
// 2. 创建路由实例并传递 `routes` 配置
const router = createRouter({
// 2. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: createWebHashHistory(),
routes // `routes: routes` 的缩写
})
// 3.新增全局前置守卫,打印目标路由对象
router.beforeEach((to, from, next) => {
console.log(to)
next();
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
})
export default router
映射组件内声明 meta
属性内容,通过全局守卫获取 meta
值。
访问 “/about”、“/” 分别打印:
13. 动态路由
前面章节路由设置都是硬代码写死,而实际项目中,路由的配置都是动态的,比如,路由设置在数据库,需要请求后台返回的数据动态设置路由。
动态路由主要通过两个函数实现。router.addRoute()
和 router.removeRoute()
。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push()
或 router.replace()
来手动导航,才能显示该新路由。
详尽的案例一定要去官网查看api文档: Vue Router-Vue.js 的官方路由