目录
一、vue router的安装与简单使用
1、Vue Router的安装
使用vue cli创建一个示例的vue项目工程,使用终端在根目录下执行如下指令来安装vue-router
npm install vue-router@4
2、一个简单的vue router使用示例
在工程的components文件夹下新建两个文件,分别命名为demo1.vue和demo2.vue
demo1.vue
<template>
<h1>我是页面一</h1>
</template>
<script>
export default {
}
</script>
demo2.vue
<template>
<h1>我是页面二</h1>
</template>
<script>
export default {
}
</script>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
//导入vue-router模块中的createRouter和createWebHashHistory方法
import { createRouter, createWebHashHistory } from 'vue-router'
//导入路由需要用到的自定义组件
import Demo1 from './components/Demo1.vue'
import Demo2 from './components/Demo2.vue'
const app = createApp(App)
//定义路由
const routes = [
{ path: '/demo1', component: Demo1 },
{ path: '/demo2', component: Demo2 }
]
//创建路由对象
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
//注册路由
app.use(router)
app.mount('#app')
运行上面代码
二、带参数的动态路由
1、路由参数匹配
在工程的components文件夹下新建一个名为User.vue的文件
<template>
<h2>姓名:{{$route.params.username}}</h2>
<h2>id:{{$route.params.id}}</h2>
</template>
在组件内部可以使用$route属性获取全局的路由对象,路由中定义的参数可以在此对象的params属性中获取到
import User from './components/User.vue'
{ path: '/user/:username/:id', component: User }
在定义路由的路径path时,使用冒号来标记参数,username和id都是路由的参数
现在在浏览器中输入如下格式的地址
结果:
需要注意的是,在使用带参数的路由时,对应相同组件的路由在进行导航切换时,相同的组件并不会被销毁再创建,这种复用机制使得页面的加载效率更高。但这也表明,页面切换时,组件的生命周期方法都不会被再次调用,如果需要通过路由参数来请求数据,之后渲染页面需要特别注意,不能再生命周期方法中进行数据请求逻辑。
App.vue组件的模板修改如下
<router-link to="/user/小王/8888">小王</router-link>
<br>
<router-link to="/user/小李/6666">小李</router-link>
user.vue的代码修改如下
mounted() {
alert(`组件加载,请求数据。路由参数为name${this.$route.params.username},路由参数为id${this.$route.params.id}`)
}
}
alert组件只有在第一次加载时才会调用,在同一组件中切换路由时, mounted不会被调用
对于这种场景,我们可以采用导航守卫的方法来处理,每次路由参数都会更新,都会回调守卫函数。
user.vue的代码修改如下
//to是更新后的路由对象,from是更新前的路由对象
beforeRouteUpdate(to, from) {
alert(`组件加载,请求数据。路由参数为name${to.params.username},路由参数为id${to.params.id}`)
}
2、路由匹配的语法规则
在进行路由参数匹配时,vue router允许参数内部使用正则表达式来匹配。
user组件的代码修改
<h1>用户中心</h1>
<h2>姓名:{{$route.params.username}}</h2>
新建名为UserSetting.vue的文件
<h1>用户设置</h1>
<h2>id:{{$route.params.id}}</h2>
在main.js文件中定义路由
{ path: '/user/:username', component: User },
{ path: '/user/:id', component: UserSetting }
上面的代码中定义的两个路由参数除了参数名不同外,格式完全一样。这种情况下,我们无法访问用户设置页面,所有符合UserSetting组件的路由规则同时也符合User组件。为了解决这一问题,最简单的方式是加一个静态的前缀路径。
{ path: '/user/info/:username', component: User },
{ path: '/user/setting/:id', component: UserSetting }
这是一个好方法,但并不是唯一的方法。
对于本例来说,用户中心页面的用户设置页面所需要参数的类型有明显的差异,用户编号必需是数值,用户则不能是纯数字。因此,可以通过正则约束来实现不同类型的参数匹配到相应的路由组件。
{ path: '/user/:username', component: User },
{ path: '/user/:id(\\d+)', component: UserSetting }
结果:
3、路由的嵌套
渲染路由的地方只有一个,即只有一个<router-view> </router-view>出口,这类路由实际上都是顶级路由。在实际开发中,我们的项目可能非常复杂,除了根组件中需要路由外,一些子组件中可能也需要路由,vue router提供了嵌套路由技术来支持这类场景。
在components文件夹下新建一个名为Friends.vue的文件。
<template>
<h1>好友列表</h1>
<h1>好友人数:{{$route.params.count}}</h1>
</template>
Friends组件只会在用户中心使用,可以将其作为一个子路由进行定义。
在User.vue中添加<router-view> </router-view>出口
<RouterView></RouterView>
User组件本身也是由路由管理的,在User组件内部再使用的 <router-view> </router-view>标签实际上定义的是二级路由的页面出口。
在main.js中定义二级路由
import Friends from './components/Friends.vue'
{
path: '/user/:username', component: User, children: [
{
path: 'friends/:count',
component: Friends
}
]
},
在浏览器中输入:
http://localhost:3000/#/user/小王/friends/6
结果:
三、页面导航
1、使用路由的方法
当我们成功向vue应用注册路由后,在任何vue实例中,都可以通过$route属性访问路由对象。通过调用路由对象的push方法可以向history栈中添加一个新的记录。也就是说,用户可以通过浏览器的返回按钮返回上一个路由的URL。
修改App.vue文件代码如下
<el-button type="primary" @click="toUser">用户中心</el-button>
toUser() {
this.$router.push({
path: "/user/小王"
})
}
结果:
push方法可以接受一个对象,对象中通过path属性配置其URL路径。push方法也支持直接传入一个字符串作为URL路径
this.$router.push("/user/小王")
也可以通过路由名加参数的方式让vue router自动生成URL,要使用这种方法进行路由跳转,在定义路由的时候需要对路由进行命名
const routes=[
path:'/user/:username?',
name:'user',
component:User
]
this.$router.push({
name:'user',
params:{
username:'小王'
}
})
如果路由需要查询参数,可以通过query属性进行设置
this.$route.push({
path:'/user',
query:{
name:'xixi'
}
})
会被处理成/user?name=xixi
需要注意,在调用push方法配置路由对象时,如果设置了path属性,则params属性会被自动忽略,push方法本身也会返回一个promise对象,可以用其来处理路由跳转成功后的逻辑
this.$router.push({
path: "/user/小王"
}).then(() => {
this.$message({
message: '跳转成功',
type: "success"
})
}, (reason) => { })
四、关于路由的命名
1、使用名称进行路由切换
router-link组件和push方法都可以根据名称进行路由切换。
this.$router.push({
name:'user',
params:{
username:'小王'
}
})
等价于
<router-link :to="{name:'user',params:{username:'小王'}}">小王</router-link>
2、路由视图命名
路由视图命名是指对router-view组件进行命名,router-view组件用来定义路由组件的出口,前面我们说过,路由支持嵌套,router-view可以进行嵌套。通过嵌套,允许我们的vue应用中出现读个router-view组件。但是对于有些场景,可能需要同级地展示多个路由视图,如顶部导航区和内容区两部分都需要使用路由组件,这时候就需要同级地使用router-view组件,要定义同级的每个router-view要展示的组件,可以对其进行命名。修改APP.vue文件。
<el-container>
<el-header>
<RouterView name="topBar"></RouterView>
</el-header>
<el-main>
<RouterView name="main"></RouterView>
</el-main>
<el-footer>
<RouterView></RouterView>
</el-footer>
</el-container>
修改main.js文件
{
path: '/',
components: {
topBar: User,
main: UserSetting,
default: Demo1
}
}
结果:
3、使用别名
别名提供了一种路由路径映射的方式,也就是说我们可以自由地将组件映射到一个任意的路径上,而不用受到嵌套解构的限制。
{ path: '/user/:id(\\d+)', component: UserSetting, alias: '/setting/:id' },
两个路径的页面效果一模一样
http://localhost:3000/#/user/666
http://localhost:3000/#/setting/666
需要注意,别名和重定向并不完全一样,别名不会改变用户在浏览器中输入路径的本身,对于多级嵌套的路由来说,可以使用别名在路径上对其进行简化。如果员路由有参数配置,一定要注意别名也需要对应地包含这些参数。
const routes=[
{path:'/user/:id(\\d+)',component:UserSetting,alias:['/setting/:id','/s/:id']}
]
4、路由重定向
重定向也是通过路由配置来完成的,与别名的区别在于,重定向会将当前路由映射到另一个路由上,页面的URL会产生改变。
当用户访问路由/d时,会自动跳转到/demo1
{ path: '/d', redirect: '/demo1' },
redirect也支持配置为对象,设置对象的name属性,设置对象的name属性可以直接指定命名路由
{ path: '/d', redirect: { name: 'Demo' } },
{ path: '/demo1', component: Demo1, name: 'Demo' },
在实际开发中,更多时候会采用动态的方式配置重定向,对于需要用户登录才能访问的页面,为登录的用户访问此路由时,我们自动将其重定向到登录页面
{
path: '/d', redirect: to => {
//to是路由对象
console.log(to);
//随机数模拟登录状态
let login = Math.random() > 0.5
console.log(login);
if (login) {
return { path: '/demo1' }
} else {
return { path: '/demo2' }
}
}
},
五、关于路由传参
在路由跳转时,可以通过参数的传递来进行后续的逻辑处理,在组件内部,之前使用$route.params的方式来获取路由传递的参数,这种方式虽然可行,但组件与路由紧紧地耦合在一起,并不利于组件的复用性。
由于之前在组件的模板内部使用了$route属性,这导致此组件的通用性大大降低,首先将其所有耦合路由的地方去掉
<template>
<h1>用户设置</h1>
<h2>id:{{id}}</h2>
</template>
<script>
export default {
props: [
'id'
]
}
</script>
现在,UserSetting组件能够通过外部传递的属性来做内部的逻辑,后面需要做的只是将路由的传参映射到外部属性上。vue router默认支持这一功能,路由配置方式如下:
{ path: '/user/:id(\\d+)', component: UserSetting, alias: '/setting/:id',props:true },
对于有多个页面出口的同级命名视图,我们需要对每个视图的props单独进行设置
{
path: '/',
components: {
topBar: User,
main: UserSetting,
default: Demo1
},
props: { topBar: true, main: true, default: true }
}
如果组件内部需要的参数与路由本身并没有直接关系,也可以将props设置为对象,此时props设置的数据将原样传递给组件的外部属性。
{ path: '/user/:id(\\d+)', component: UserSetting,props:{id:'000'}},
如上图所示,此时路由中的参数将被弃用,组件中获取到的id属性值将被固定为‘000’
props还有一种更强大的使用方式,可以直接将其设置为一个函数,函数中返回要传递到组件的外部属性对象,这种方式动态性很好
{ path: '/user/:id(\\d+)', component: UserSetting, alias: '/setting/:id', props:route=>{
return{
id:route.params.id,
}
} },
六、路由导航守卫
导航守卫的主要作用是在进行路由跳转时决定通过此次跳转或拒接此次跳转。
1、定义全局导航守卫
我们使用createRouter方法来创建路由实例,此路由实例可以使用beforeEach方法来注册全局的前置导航守卫,之后当触发导航跳转时,都会被此导航守卫捕获。
router.beforeEach((to, from) => {
console.log(to);//将要跳转的路由对象
console.log(from);//当前将要离开的路由对象
return false//返回true表示允许此次跳转,返回false表示禁止此次跳转
})
当注册的beforeEach方法返回的是布尔值时,期用来决定是否允许此次导航跳转,以上代码表示,所有路由跳转都被禁止。
更多时候,我们会在beforeEach方法中返回一个路由配置对象来决定要跳转的页面,这种方式更加灵活,例如可以将登录态校验的逻辑放在全局守卫中处理
{
path: '/user/:id(\\d+)', name: 'setting', component: UserSetting, alias: '/setting/:id', props: route => {
return {
id: route.params.id,
}
}
},
router.beforeEach((to, from) => {
console.log(to);//将要跳转的路由对象
console.log(from);//当前将要离开的路由对象
/* return false//返回true表示允许此次跳转,返回false表示禁止此次跳转 */
if (to.name != 'setting') {
return { name: 'setting', params: { id: '000' } }//返回要跳转到的2路由
}
})
与定义全局前置守卫类似,也可以注册全局的导航后置回调。与前置守卫不同的是,后置回调不会改变导航本身,但是其对页面的分析和监控十分有用。
router.afterEach((to, from, failure) => {
console.log('跳转结束');
console.log(to);
console.log(from);
console.log(failure);
})
结果:
failure参数可以记录导航的异常信息。
2、为特定的路由注册注册导航守卫
如果只有特定的场景需要在页面跳转过程中实现相关逻辑,也可以为指定的路由注册导航守卫。有两种注册方式,一种是在配置路由时进行定义,另一种是在组件中定义。
path: '/demo1', component: Demo1, name: 'Demo', beforeEnter: (to, from, next) => {
return false
}
当用户访问/demo1路由对应的组件都会被拒接掉,需要注意,beforeEnter设置的守卫只有在进入路由时会触发,路由的参数变化并不会触发此守卫。
在编写组件时,也可以实现一些方法来为组件定制守卫函数
<template>
<h1>我是页面二</h1>
</template>
<script>
export default {
beforeRouteEnter: (to, from, next) => {
console.log(to, from, '前置守卫');
},
beforeRouteUpdate: (to, from, next) => {
console.log(to, from, '路由参数有更新时的守卫');
},
beforeRouteLeave: (to, from, next) => {
console.log(to, from, '离开页面');
},
}
</script>
结果:
beforeRouteEnter是组件的导航前置守卫,在通过路由将要切换到当前组件时被调用,在这个函数中,可以做拦截操作,也可以做重定向操作,需要注意此方法只有在第一次切换此组件时会被调用,路由参数的变化不会重复调用此方法。beforeRouteUpdate方法在当前路由发生变化时会被调用,例如由参数的变化等都可以在此方法中捕获。beforeRouteLeave方法会在将要离开当前页面时被调用。还有一点特别需要注意 beforeRouteEnter方法中不能使用this来获取当前组件实例,因为在导航守卫确认通过前,新的组件还没有被创建,如果一定要在导航被确认时使用当前组件实例处理一些逻辑,可以通过next参数注册回调方法。
beforeRouteEnter(to, from, next) {
console.log(to, from, '前置守卫');
next((w) => {
console.log(w);//w为当前组件实例
})
},
结果:
当前置守卫确认了此次跳转后,next参数注册的回调方法会被执行,并且会将当前组件的实例作为参数传入,在beforeRouteUpdate和beforeRouteLeave方法中可以直接使用this关键字来获取当前组件实例。
七、动态路由
1、动态添加与删除路由
在Vue Route中,动态操作路由的方法主要有两个:addRoute和removeRoute,addRoute用来动态添加一条路由,对应的removeRoute用来动态删除一条路由。
修改Demo1.vue
<template>
<h1>我是页面一</h1>
<el-button type="primary" @click="click">跳转demo2</el-button>
</template>
<script>
import Demo2 from './Demo2.vue'
export default {
created() {
console.log(this);
this.$router.addRoute({
path: '/demo2',
component: Demo2
})
},
methods: {
click() {
this.$router.push('/demo2')
}
}
}
</script>
修改main.js文件
{
path: '/demo1', component: Demo1, name: 'Demo', /* beforeEnter: (to, from, next) => {
return false
} */
结果:
以下这几种场景会触发路由的删除。
当使用addRoute方法动态添加路由时,如果添加了重名的路由,旧的会被删除
在调用addRoute方法时,他其实会返回一个删除回调,我们可以通过此删除回调来直接删除所添加的路由。
let call = this.$router.addRoute({
path: '/demo2',
component: Demo2,
name: 'Demo2'
})
call()//删除此路由
对于已经命名的路由,可以通过名称来对路由进行删除
<template>
<h1>我是页面一</h1>
<el-button type="primary" @click="click">跳转demo2</el-button>
<el-button type="danger" @click="remove">删除demo2动态路由</el-button>
</template>
<script>
import Demo2 from './Demo2.vue'
export default {
created() {
console.log(this);
this.$router.addRoute({
path: '/demo2',
component: Demo2,
name: 'Demo2'
})
},
methods: {
click() {
this.$router.push('/demo2')
},
remove() {
this.$router.removeRoute('Demo2')
}
}
}
</script>
结果:
没有删除前:
删除后:
路由被删除时,其所有的别名和子路由也会同步被删除
hasRouter方法用来检查当前已经注册的路由中是否包含某个路由,getRoutes方法用来获取包含所有路由的列表。