Vue - Router
- 理解: 一个路由(
route
)就是一组映射关系(key - value),多个路由需要路由器(router
)进行管理。 - 前端路由:key是路径,value是组件。
那么为什么需要Vue
中提供的Router
呢?
我们知道,在以前的网站当作,页面可以说全部是多页面应用;比如你当前在index.html
这个页面,当你点击一个链接之后,浏览器可能会新开一个页签,然后你可能就来到了user.html
这个页面,也就是说,在一个web应用系统中,是由多个html
页面组成的,你点击不同的链接,就跳转到不同的页面。但是这样也会带来很多问题,最大的一个影响就是页面会抖动,也就是浏览器那个刷新按钮会转动。
那么在Vue
项目中,我们知道,一个Vue项目
基本上都是单页面应用,也就是只包含一个html
页面,也就是index.html
页面,当你如果点击链接之后,由于只有一个html
页面,不会出现页面的抖动,但是页面的呈现却要变化,这其实是通过展示不同的组件来完成的。
那么如何实现点击不同的链接,呈现出的是不同的组件并且页面也不跳转刷新呢?这就要用到我们的Vue Router
了。
1. 基本使用
使用步骤:
安装Vue Router
Vue Router
其实是一个插件,需要自己去安装。注意:在Vue2中你只能安装Router3及其以下版本,在往高了去,其实Vue2就不支持了,要Vue3才支持。
npm i vue-router@3
应用插件
要在
main.js
文件中应用插件:import VueRouter from "vue-router"; //引入插件 Vue.use(VueRouter);//使用插件
编写Router配置项
之前我们说过,点击不同的链接跳转到不同的组件,对于链接来说,也就是地址(key);对于组件来说,也就是value,key和value的组合才是一个路由(route);多个路由(route)在一起,才组成了路由器(router)。
现在我们要来配置这些key-value,也就是一个个route,前提是你的相关组件也要准备好。
现在的项目结构:
现在不用过多关心组件内容,先把要用的组件准备好,并且,编写Router配置项的时候,我们一般是在src下新建router文件夹,下面再建index.js文件。
配置内容:
//1. 引入VueRouter import VueRouter from "vue-router"; //2. 引入相关的组件 import Home from "@/components/Home.vue"; import About from "@/components/About.vue"; //3. 创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ //初始化时,默认显示页面 { path: "/", redirect: "/about", //重定向到about路径 }, { path:'/about', component:About }, { path:'/home', component:Home } ] }); //4. 将router保暴露出去 export default router;
router配置
router构建好了之后,还要配置到main.js文件中去,当你引入了VueRouter插件之后,你就可以在Vue实例身上配置一个全新的配置项:router。
import Vue from 'vue' import App from './App.vue' //引入VueRouter插件 import VueRouter from "vue-router"; //引入路由器 import router from "@/router"; //应用VueRouter插件 Vue.use(VueRouter); Vue.config.productionTip = false new Vue({ render: h => h(App), router:router //配置router配置项 }).$mount('#app')
标签切换
将以前点击链接的地方,也就是
a
标签,换为router-link
标签。例如以前是:
<a class="active" href="about.html">about.html</router-link>
上面的href里面的地址没有写全,不过以前基本上都是…页面名称.html结尾。
现在:
<router-link active-class="active" to="/about">About</router-link>
其中
active-class
表示点击时加上的样式,to
表示要到达的路由key
,然后VueRouter
会根据这个key找到对应的组件,之后渲染页面。上面的标签属性都属于
VueRouter
里面的。注意:再vue重新渲染页面之后,你的router-link标签会被解析为a标签的。
指定展示位置
现在可以找到对应的组件了,但是你没有指定这个组件应该放到什么位置,就好像插槽那一章一样,在插槽中,你用
slot
标签指定插槽放入页面结构的位置,所以在VueRouter
中,你也要使用一个标签来存放组件的结构,这里使用router-view
标签:<router-view></router-view>
了解了基本的使用步骤之后,现在写个基本案例看看:
<template>
<div class="app">
<div class="nav">
<a href="javascript:;" @click="clickAbout" :class="{'active': needActive === 'About'}">About</a>
<hr>
<a href="javascript:;" @click="clickHome" :class="{'active': needActive === 'Home'}">Home</a>
</div>
<div class="content">
<About v-show="needActive === 'About'"/>
<Home v-show="needActive === 'Home'"/>
</div>
</div>
</template>
<script>
import About from "@/components/About.vue";
import Home from "@/components/Home.vue";
export default {
name: 'App',
components: {
About,
Home,
},
data(){
return {
needActive: 'About' //记录当前需要激活active样式的组件
}
},
methods:{
clickAbout(){
this.needActive = 'About';
},
clickHome(){
this.needActive = 'Home'
}
}
};
</script>
<style>
.app {
width: 600px;
}
.nav {
width: 200px;
border: 1px solid black;
float: left;
}
.nav a {
display: block;
text-align: center;
text-decoration: none;
margin-top: 0;
}
.content {
width: 200px;
float: left;
}
.active {
background-color: skyblue;
}
</style>
执行结果:
通过点击不同的导航栏可以在右侧展示不同的内容。注意,按照以前传统的方式来说,不同链接的地址应该为对应的html页面, 只是我们这里做了特殊处理,因为是在Vue项目中编写的,所有没有这么多html,不过意思是这么个意思。并且如果你的链接的地方写了不同的html页面之后,浏览器是会刷新的。
现在要改为VueRouter
的形式,我们注意观察地址栏和浏览器是否刷新:
<template>
<div class="app">
<div class="nav">
<router-link to="/about" active-class="active">About</router-link>
<hr>
<router-link to="/home" active-class="active">Home</router-link>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
/*样式文件不变*/
</style>
执行结果:
可见页面默认展示的时About
组件,并且,端口号后面有一个#
号,这是和VueRouter
的路由模式有关,这里先不做探讨。
并且对于router-link
标签,他底层其实又转为了a
标签:
然后以前写选择样式的时候,点击了某个连接,导航栏的背景就要变,以前时通过写点击事件来动态绑定样式的,现在你只需要使用active-class
标签属性就能够实现样式的切换。并且对于页面的呈现,使用router-view
标签即可。
2. 几个注意点
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹就好比之前我们的
About
、Home
组件,都属于路由组件,也就是在index.js
中配置了的组件,都属于路由组件。通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
改造Home组件:
export default { name: 'Home', mounted() { console.log('Home组件挂载了....'); }, beforeDestroy() { console.log('Home组件销毁了....'); } }
改造About组件:
export default { name: 'About', mounted() { console.log('About组件挂载了....'); }, beforeDestroy() { console.log('About组件销毁了....'); } }
执行结果:
当我切换为Home的时候注意查看:
每个组件都有自己的
$route
属性,里面存储着自己的路由信息我们在路由组件挂载的时候看看
this
上面都有啥。每一个路由组件上都有
route
,并且不同的路由组件里面存放的信息是不一样的mounted() { console.log('About组件挂载了....',this); window.aboutRoute = this.$route; window.aboutRouter = this.$router; }
mounted() { console.log('Home组件挂载了....',this); window.homeRoute = this.$route; window.homeRouter = this.$router; }
整个应用只有一个
router
,可以通过组件的$router
属性获取到对于上面的也看见了,虽然路由组件不同,但是他们的
router
是相同的。
3. 嵌套路由(多级路由)
配置路由规则,使用
children
配置项:routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
注意:在多级路由的时候,子路由(children)中的
path
千万不要写/
4. 路由的query参数
我们都知道,在一个网址路径后面,是可以使用?xxx=yyy
的条件形式的,并且多个条件之间使用&
分隔。
例如:https://demo.net/survey/detail?id=440
;其中的id=440
就是条件,条件与路由使用?
分隔。
在VueRouter中使用query参数:
to的字符串写法
<router-link to="/about?id=666&title=关于" active-class="active">About</router-link>
可见可以直接在路径后面跟上条件即可。执行结果:
可见传参成功,并且在
route
中有对应的query
配置。上述是传递静态的参数,动态的参数传递如下:
<router-link :to="`/about?id=${id}&title=${title}`" active-class="active">About</router-link>
export default { name: 'App', data() { return { id: '666', title: '关于' } } };
执行结果也为一样,不过要注意的是,传递动态参数的时候里面是用的反引号,并且是v-on:to;
to的对象写法
除了字符串写法,还有一种是对象写法:
<router-link :to="{ path:'/about', query:{ id:666, title:'关于' } }" active-class="active" > About </router-link>
新增了两个配置参数:
path
和query
。其中
path
还是正常的写路径即可,query
处就写你想要传递的参数信息。注意,这里的to
也是需要写成动态绑定的形式:to
接收参数
想要获取到query参数也很简单:
$route.query.id
$route.query.title
5. 命名路由
顾名思义就是给路由取名字,作用:可以简化路由的跳转。
使用步骤:
给路由取名
const router = new VueRouter({ routes:[ //初始化时,默认显示页面 { path: "/", redirect: "/about", //重定向到about路径 }, { name:'guanyu', //给路由命名 path:'/about', component:About }, { name:'zhuye', //给路由命名 path:'/home', component:Home } ] });
简化使用
<router-link :to="{ name:'guanyu', query:{ id:666, title:'关于' } }" active-class="active" > About </router-link> <hr> <router-link :to="{name:'zhuye'}" active-class="active">Home</router-link>
对于很长的路径那种情况,使用路由名称能够很好的简化。
6. 路由的params参数
之前说了路由的query
参数,现在来看看路由的parmas
参数。
使用步骤:
配置路由,声明接收
params
参数//3. 创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ //初始化时,默认显示页面 { path: "/", redirect: "/about", //重定向到about路径 }, { name:'guanyu', path:'/about', component:About }, { name:'zhuye', path:'/home/:id/:title',//使用占位符声明接收params参数 component:Home } ] });
传递参数
to的字符串写法:
<router-link to="/home/666/首页" active-class="active">Home</router-link>
to的对象写法:
<router-link :to="{ name:'zhuye', params:{ id:666, title:'首页' } }" active-class="active" > Home </router-link>
执行结果:
可见params配置项中有值了。
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
7. 路由的props配置
作用:让路由组件更方便的收到参数
以前我们学了父子组件之间使用props
传递数据,现在在路由这里,也可以用props
配置传递数据。
props配置是写在路由配置中的,也就是index.js文件中,并且写在哪个路由里面,就代表传递数据给哪个组件。
例如:
const router = new VueRouter({
routes:[
//初始化时,默认显示页面
{
path: "/",
redirect: "/about",
},
{
name:'guanyu',
path:'/about',
component:About,
props:{a:1,b:'哈哈'} //使用props配置项传递数据给About组件
},
{
name:'zhuye',
path:'/home/:id/:title',
component:Home
}
]
});
About.vue
:
<template>
<div>
我是About中的内容....<br/>
我收到的数据:a={{a}}; b={{b}}
</div>
</template>
<script>
export default {
name: 'About',
props:['a','b'], //注意:这里仍然是需要用props来接收的。
mounted() {
console.log('About组件挂载了....',this);
}
}
</script>
<style scoped>
</style>
执行结果:
props配置一共有三种写法:
props
值为对象,该对象中所有的key-value
的组合最终都会通过props
传给组件{ name:'guanyu', path:'/about', component:About, props:{a:1,b:'哈哈'} //使用props配置项传递数据给About组件 },
这种刚刚已经演示过了,就不再演示了。
props
值为布尔值,布尔值为true,则把路由收到的所有**params
**参数通过props
传给组件{ name: 'zhuye', path: '/home/:id/:title',//使用占位符声明接收params参数 component: Home, props: true //使用props配置项传递数据给Home组件 }
Home.vue
:<template> <div> 我是Home中的内容.... 收到的数据为:id={{id}};title={{title}} </div> </template> <script> export default { name: 'Home', props:['id','title'], mounted() { console.log('Home组件挂载了....',this); } } </script> <style scoped> </style>
执行结果:
props
值为函数,该函数返回的对象中每一组key-value
都会通过props
传给组件props(route) { return { id: route.params.id, title: route.params.title, } }
同样能够收到数据。
说明:如果
props
值为函数的时候,能够收到一个参数,这个参数就是每个路由组件的route
对象。如果你是通过query
传递的参数,那么再props
函数中,你就要用route.query.xxx
,如果是params传递的参数,那么就用route.params.xxx
。
8. <router-link>
的replace属性
作用:控制路由跳转时操作浏览器历史记录的模式
也就是通过浏览器上面两个箭头来操作的浏览器历史记录。
浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
对于浏览器来说,默认就是
push
的方式,也就是将你的浏览记录依次入栈,然后通过一个指针来访问浏览记录。类似于下图:通过浏览器的左箭头就是回退,右箭头就是前进。
如果你是用的
replace
模式,那么就是这样的场景:新来的记录会将以前的记录给**替换(覆盖)**,所以,此时你无法回退也无法前进。
如何开启
replace
模式直接再
router-link
标签上写上replace
标签属性即可。<router-link :to="/home" active-class="active" replace>Home</router-link>
9. 编程式路由导航
以前我们使用路由跳转使用的是router-link
标签,但是我们知道,router-link
在被Vue
解析了之后,会变成普通的a
标签,所以,一般我们再写路由的时候,明确知道是通过链接来实现的时候,一般我们就用router-link
就可以了,但是有时候我们想实现点击一个按钮Button
标签、块div
标签等来实现路由的切换,这时候就不可以使用router-link
了,那这时候该使用什么东西来完成路由的切换呢。
所以,编程式路由导航就是这样产生的,作用:不借助<router-link>
实现路由跳转,让路由跳转更加灵活
我们可以使用$router
身上的一些API来实现任意路由的切换:
this.$router.push
对应浏览器记录的
push
模式,通过向浏览器记录栈中追加你输入的URL:this.$router.push({ name:'路由名称', params:{ id:xxx, title:xxx } })
this.$router.replace
对应浏览器记录的
replace
模式,将浏览器记录栈中当前的URL替换为你输入的URL:this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } })
以上两种方式可以导航到任意的路由,并且不仅限于a标签可使用,所有的标签都可以。
另外还有一些API可以实现浏览器的回退和前进按钮:
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退,可接受参数,参数的值就是回退或前进的步数
10. 缓存路由组件
有时候我们有这种情况:
当我们在一个路由组件中输入了一些值,然后切换了路由之后再回来的时候,输入框中的值就被清空了。这是因为当你切走路由组件的时候,对应的组件会被销毁,然后切进路由组件的时候,对应的组件会被创建。
所以,如果你想要保存下输入框的值,你就要保证切换路由组件的时候,保证前一个组件不被销毁,这就是缓存路由组件的作用。
作用:让不展示的路由组件保持挂载,不被销毁。
通过keep-alive
标签实现缓存路由组件,通过include
标签属性来指定要缓存的路由组件。
例如:
<keep-alive include="About">
<router-view></router-view>
</keep-alive>
注意:
keep-alive
标签是写再router-view
标签外层的,不是写在其他标签外层。- 如果不写include属性标签表示缓存所有的组件。
- 如果是数组:
:include="['About','Home']"
。
可见当离开About
路由,进入Home
路由的时候,About
组件被挂起了,并为被销毁,反观Home
组件切回来的时候被销毁了。
11. 两个新的生命周期钩子
之前我们学习了Vue的生命周期钩子,但是没有学完,这里再学两个新的生命周期钩子。
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
现在我有这样的需求:在About
路由组件被缓存的前提下,要求切入About
路由组件的时候,定时器执行,离开About
路由组件切进Home
路由组件的时候定时器销毁。
如果使用已有的Vue生命周期钩子,代码如下:
About.vue
:
mounted() {
console.log('About组件挂载了....定时器要开始执行');
this.timerId = setInterval(()=>{
console.log('@');
},50);
},
beforeDestroy() {
console.log('About组件将要被销毁了....定时器要被销毁')
clearInterval(this.timerId);
}
执行结果可见,当我切走About组件的时候,由于该路由组件被缓存了,并没有执行beforeDestroy
方法,这时候应该怎么办呢,我既想要保留下输入框中的值,又想在切走该路由组件的时候停止这个定时器。
这时候就要引出两个新的生命周期钩子了:
activated
路由组件被激活时触发,也就是切入时。deactivated
路由组件失活时触发,也就是切出时。
改造About.vue
代码:
activated() {
console.log('About组件被激活了....定时器要开始执行');
this.timerId = setInterval(()=>{
console.log('@');
},50);
},
deactivated() {
console.log('About组件失活了了....定时器要被销毁')
clearInterval(this.timerId);
}
这时候你再去测试,发现About路由组件切走的时候定时器被销毁了,但是你切回来的时候,输入框中的内容并没有丢失,定时器也重新开启了。
12. 路由守卫
作用:对路由进行权限控制;例如哪些用户可以查看这个页面。
分类:全局守卫、独享守卫、组件内守卫
12.1 全局守卫
全局路由守卫配置是放到路由器配置文件中的,也就是index.js
中。
全局路由守卫中,又分为前置路由守卫和后置路由守卫:
前置路由守卫
你构建的
VueRouter
是什么名称这里就用名称.beforeEach
;例如我构建
VueRouter
时://3. 创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes: [ //初始化时,默认显示页面 { path: "/", redirect: "/about", //重定向到about路径 }, { name: 'guanyu', path: '/about', component: About, props: {a: 1, b: '哈哈'} }, { name: 'zhuye', path: '/home/:id/:title',//使用占位符声明接收params参数 component: Home, props(route) { return { id: route.params.id, title: route.params.title, } } } ] });
我使用的时
router
实例对象,那么这里我就使用router.beforeEach
来进行前置路由守卫。其回调函数可以收到3个参数,分别为
to
、from
、next
- to表示从哪里来,来至于哪个路由
- from表示到哪去,值为目标路由地址
- next表示放行操作
例如:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'B站大学'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } })
补充:
我上述代码中使用了
meta
,meta
代表的是路由元数据,一般是程序员自己写的;每个路由都有一个 meta 元数据字段, 我们可以在这里设置一些自定义信息,供页面组件或者路由钩子函数中使用。前置路由守卫执行时机:
- 初始化时执行
- 每次路由切换前执行
后置路由守卫
参考前置路由守卫,对应的,这里还有后置路由守卫,使用
afterEach
这个API,其回调函数只能接收两个参数:to
、from
这两个参数的含义与前置路由一致。
//全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
1.2 独享守卫
独享守卫是写在了具体某个路由中,只为该路由服务,对应的API为beforeEnter
,例如:
{
name: 'guanyu',
path: '/about',
component: About,
props: {a: 1, b: '哈哈'},
meta: {isAuth: true}, //自定义数据
beforeEnter(to, from, next) {
console.log('beforeEnter', to, from)
if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制
if (localStorage.getItem('school') === 'atguigu') {
next()
} else {
alert('暂无权限查看')
// next({name:'guanyu'})
}
} else {
next()
}
}
},
注意:独享路由守卫虽然API是beforeEnter,但是并没有afterEnter。
1.3 组件内守卫
组件内守卫是配置到具体的组件中的。组件内守卫又分为进入守卫和离开守卫
进入守卫
通过路由规则,进入该组件时被调用
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { },
离开守卫
通过路由规则,离开该组件时被调用
//离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }
注意:beforeRouteEnter
、beforeRouteLeave
这两个方法和生命周期钩子,data
、components
配置项都是平级的关系。
13. 路由器的两种工作模式
对于一个
url
来说,什么是hash
值?——#
及其后面的内容就是hash值。hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
也就是你请求数据的时候,不会把
#
号后边的内容当作地址发送给服务器请求数据hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
配置方式:
在构建路由器的时候,有一个mode配置项:
//3. 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
mode: 'history', //配置为history模式,不写默认为hash模式
routes: [
//众多路由...
]
});