一、v-router插件
1.v-router插件介绍
v-router是vue的一个核心插件,vue+vue-router主要用来做SPA(单页面应用)的。
什么是SPA:就是在一个页面中,有多个页签,我们选择页签显示不同的内容,但页面不跳转。
例如:
在网易云音乐的主页中,发现音乐、我的音乐、朋友这三个页签就是单页面应用。当我们切换他们时,可以观察到url的变化:
这里看到的url改变(路由改变)的路由是vue-router提供的,而不是对应django的路由系统(前后端分离的项目,django只提供API的路由)。
2.下载vue-router
vue-router官方文档:https://router.vuejs.org/zh/
下载vue-router.js:https://unpkg.com/vue-router/dist/vue-router.js
由于vue-router是依赖vue.js库的,所以要先引入vue.js:
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
二、vue-router使用
1.使用vue-router实现页签切换
<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
<script>
// 主页组件
const Home = {
data() {
return {}
},
template: `
<div>
我是Home
</div>
`
}
// 课程页面组件
const Course = {
data() {
return {}
},
template: `
<div>
我是Course
</div>
`
}
// VueRouter实例,其中的routes列表属性中定义具体的路由映射,url-->组件
const router = new VueRouter({
routes: [
{
path: '/',
component: Home
},
{
path: '/course',
component: Course
}
] })
let App = {
data() {
return {}
},
// 最前面一个header,提供标签,标签不是<a>标签,而是<router-link>
// 该标签可以通过to属性的url在routes中匹配到对应的路由,然后找到对应组件,渲染到<router-view>的位置
template: `
<div>
<div class="header">
<router-link to = "/">首页</router-link>
<router-link to = "/course">课程</router-link>
</div>
<router-view></router-view>
</div>
`
}
var vm = new Vue({
el: "#app",
data() {
return {}
},
router,
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
})
</script>
</body>
其中最重要的就是VueRouter实例中的routes的配置,以及App组件中使用的vue-router提供的两个全局组件<router-link>和<router-view>。
<router-link>标签默认被渲染成a标签,而其中的to属性默认被渲染为a标签的href属性。
还有个最重要的就是,vue实例中的router属性,实际上是router:router(名字相同可以缩写为router)。
实现效果:
加上一些简单样式后,效果更明显:
2.路由重定向
修改路由实例router,实现重定向:
const router = new VueRouter({
routes: [
// 当访问"/"目录时,重定向到/home
{
path: '/',
redirect:'/home'
},
{
path: '/home',
component: Home
},
{
path: '/course',
component: Course
}
]
})
3.命名路由
给每条路由条目添加name:
const router = new VueRouter({
routes: [
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/course',
name: 'Course',
component: Course
}
]
})
使用的时候:
let App = {
data() {
return {
home_page_name:'Home',
course_page_name:'Course'
}
},
template: `
<div>
<div class="header">
<router-link class="header_button" :to = "{name:home_page_name}">首页</router-link>
<router-link class="header_button" :to = "{name:course_page_name}">课程</router-link>
</div>
<router-view></router-view>
</div>
`
}
注意,这里使用了v-bind绑定了to属性,后面的对象就可以使用动态数据来指定组件了,当然也可以使用静态的字符串。例如"{name:'Home'}"。
4.动态路由匹配
直白的说就是给路由中传入动态参数,例如http://127.0.0.1:8080/user/1是访问id为1的用户信息,最后的1代表user_id,每个用户访问这个页面的路由都不一样。
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Gouzi</title>
<style>
.content{
width:400px;
height: 200px;
}
.header_button{
width: 30px;
height: 20px;
line-height: 20px;
background-color: brown;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
<script>
// 用户detail页面组件
const User = {
data() {
return {
user_id:null
}
},
template: `
<div class="content" style="background-color: darkgoldenrod">
我是User-{{user_id}}页面
</div>
`,
// 想要获得当前路由url中的id,使用以下操作:1.created(测试不成功)2.使用watch(成功)
// 在User组件首次创建的时候,使用created获取url中的user_id
created(){
// 使用$route就可以获取当前url的路由信息,从其中可以拿到/user/1中的1
this.user_id = this.$route.params.id;
// 在这里使用ajax获取user_id对应的后台数据
//todo.
},
// 在我们切换页签的时候,例如/user/1切换到/user/2,created默认不执行,因为vue为了提高效率内部复用了User组件,所以User组件不会重复创建,这时我们要使用watch
watch:{
// 侦听$route的变化,然后执行下面操作
'$route'(to,from){
console.log(to); // 这个to对应/user/2的$route(我们从/user/1切换到/user/2)
console.log(from); // 这个from对应/user/1的$route(我们从/user/1切换到/user/2)
this.user_id = to.params.id;
//在这里可以发送ajax请求切换后user_id对应的用户信息
//todo.
}
}
}
// VueRouter实例,其中的routes列表属性中定义具体的路由映射,url-->组件
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/user'
},
{
path: '/user/:id', // 使用:id来接收id动态数据
name: 'User',
component: User
}
]
})
let App = {
data() {
return {
user_page_name:"User",
}
},
template: `
<div>
<div class="header">
<!-- 使用params:{id:1}来接收id参数,如果有很多用户,则使用v-for循环 -->
<router-link class="header_button" :to = "{name:user_page_name,params:{id:1}}">用户1</router-link>
<router-link class="header_button" :to = "{name:user_page_name,params:{id:2}}">用户2</router-link>
</div>
<router-view></router-view>
</div>
`
}
var vm = new Vue({
el: "#app",
data() {
return {}
},
router,
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
})
</script>
</body>
</html>
流程解释:
1)第一次打开页面时,User组件被创建,其中的created方法会被执行,我们在其中使用$route获取id,并更新user_id属性。
2)由于vue内部机制为了效率,所以在我们点击切换页签(例如/user/1切换到/user/2时),User组件不会销毁再重建,而是会被复用,所以created钩子方法不会再被执行,这时,为了获取id,我们可以使用watch侦听$route的变化,然后更新user_id。
3)在created或watch获取id后,可以通过ajax请求user_id对应的用户信息,并渲染到组件的template中(这里未实现)。
4)在路由实例router中,使用":id"来构造动态路由条目。
5)在<router-link>中,使用params:{id:1}来传递id的值给路由实例。
注意:$route是路由信息对象,而$router是我们定义的router对象(即VueRouter的实例)。为什么我们能在任何的组件中直接使用this.$route获取,这是因为我们的组件挂载到了父组件,从而继承下来的。
5.导航守卫
在4.动态路由匹配中,出现了组件复用的问题。除了前面说的对$route进行侦听以外,还可以使用导航守卫来解决该问题。
导航守卫就是vue-router的一些全局或局部的钩子函数,类似vue的钩子函数,参考:[前端] VUE基础 (5) (过滤器、生命周期、钩子函数)
1)全局守卫
在main.js中:
// 前置守卫
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => {
console.log(to);
console.log(from);
next(); // 在结束处理后,必须调用一下next,否则阻塞在这里,页面也不动了
})
beforeEach的意思就是在任意路由切换之前被调用(全局的守卫会监控所有的路由切换)。
注意,参数中的next是一个方法,我们在beforeEach中一定要调用一下next方法,否则会一直卡在这里,页面出不来。
除了前置守卫,还是后置守卫:
router.afterEach((to, from) => {
// ...
})
2)路由独享守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
路由独享守卫,就是在某个路由内部定义钩子函数,这个钩子只对该路由监控。
3)局部守卫(组件内的守卫)
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
对某个组件对应的路由生效(当某个路由对应的页面使用了该组件,该路由就会被守卫监控)。
6.query参数(?userid=1)
参考4.中传递id的方式,是这样的:
<router-link :to="{name:'User',params:{id:1}}">
我们就可以实现对 http://127.0.0.1:8080/user/1 的访问。
如果想实现对 http://127.0.0.1:8080/user?userid=1:
<router-link :to="{name:'User',query:{userid:1}}">
同样的,在组件中要获取这个userid,使用:
created(){
this.user_id = this.$route.query.userid;
},
其他操作都类似4.动态路由匹配的操作。可以参考第4节。
7.编程式导航(push、go)
在前面的章节中,我们都是通过以下形式来使用路由的:
<router-link class="header_button" :to = "{name:'Home'">主页</router-link>
这种方式叫做 "声明式导航"。
而编程式导航的意思就是在标签或组件触发的事件函数中去跳转路由。
编程式导航的完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Gouzi</title>
<style>
.content{
width:400px;
height: 200px;
}
.header_button{
width: 30px;
height: 20px;
line-height: 20px;
background-color: brown;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
<script>
// 主页
const Home={
data(){
return {}
},
template:`
<div class="content" style="background-color: darkorange">
我是主页
</div>
`
}
// 用户detail页面组件
const User = {
data() {
return {
user_id:null
}
},
// 在User页面中提供一个按钮,点击跳转到Home主页
template: `
<div class="content" style="background-color: darkgoldenrod">
我是User-{{user_id}}页面
<button @click="jumpToHome">跳转到主页</button>
</div>
`,
methods:{
jumpToHome(){
// 跳转到主页,使用$router.push()
this.$router.push({
name:'Home'
});
}
},
created(){
this.user_id = this.$route.params.id;
//todo.
},
watch:{
'$route'(to,from){
this.user_id = to.params.id;
//todo.
}
}
}
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/user'
},
{
path: '/home', // 使用:id来接收id动态数据
name: 'Home',
component: Home
},
{
path: '/user/:id', // 使用:id来接收id动态数据
name: 'User',
component: User
}
]
})
let App = {
data() {
return {
home_page_name:"Home",
user_page_name:"User"
}
},
template: `
<div>
<div class="header">
<router-link class="header_button" :to = "{name:home_page_name}">主页</router-link>
<!-- 使用params:{id:1}来接收id参数,如果有很多用户,则使用v-for循环 -->
<router-link class="header_button" :to = "{name:user_page_name,params:{id:1}}">用户1</router-link>
<router-link class="header_button" :to = "{name:user_page_name,params:{id:2}}">用户2</router-link>
</div>
<router-view></router-view>
</div>
`
}
var vm = new Vue({
el: "#app",
data() {
return {}
},
router,
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
})
</script>
</body>
</html>
编程式导航很简单。就是在某个标签或组件的事件中去跳转页面,使用路由实例$router的push方法,传入路由条目的名称即可,$router.push({name:'Home'})。
除了使用this.$router.push()可以跳转到指定的路由,使用 this.$router.go() 可以回到之前的路由:
this.$router.go(-1) // 回到之前的页面
这个功能也很有用,例如我们从一个页面跳转到登录页面,当登录成功后,需要重新回到刚才的页面,就可以使用this.$router.go(-1)。
8.嵌套路由
为了实现多级路由,例如/user/1/info和/user/1/detail。我们在路由中可以按如下定义:
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/info 匹配成功,
// UserInfo 会被渲染在 User 的 <router-view> 中
path: 'info',
component: UserInfo
},
{
// 当 /user/:id/detail 匹配成功
// UserDetail 会被渲染在 User 的 <router-view> 中
path: 'detail',
component: UserDetail
}
]
}
]
})
如代码所示,UserInfo组件和UserDetail组件都是在User组件中使用的,而User组件是在App组件中使用的。这样,我们就可以在查看user_id=1的用户页面,切换显示UserInfo或UserDetail组件。
但在这种路由情况向,如果我们只访问/user/1的话,页面不会有渲染,这是我们可以设置一个空的子路由来渲染点内容:
const router = new VueRouter({
routes: [
{
path: '/user/:id', component: User,
children: [
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome }, // ...其他子路由
]
}
]
})
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>嵌套路由</title>
<style>
.content{
width:400px;
height: 200px;
}
.header_button{
width: 30px;
height: 20px;
line-height: 20px;
background-color: brown;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
<script>
// User组件下的使用的子组件UserHome,用于请求/user/id/时
const UserHome = {
data(){
return {}
},
template:`
<div class="content" style="background-color: yellow">
我是UserHome页面
</div>
`
}
// User组件下的使用的子组件UserInfo,用于请求/user/id/info/时
const UserInfo = {
data(){
return {}
},
template:`
<div class="content" style="background-color: darkorange">
我是UserInfo子页面
</div>
`
}
// User组件下的使用的子组件UserDetail,用于请求/user/id/detail/时
const UserDetail = {
data(){
return {}
},
template:`
<div class="content" style="background-color: darkorange">
我是UserDetail子页面
</div>
`
}
// User组件
const User = {
data() {
return {
user_id:''
}
},
// 在User页面中提供一个按钮,点击跳转到Home主页
template: `
<div class="content" style="background-color: darkgoldenrod">
我是User-{{user_id}}页面
<button @click="jumpToInfo">跳转到Info</button>
<button @click="jumpToDetail">跳转到Detail</button>
<br>
<router-view></router-view>
</div>
`,
methods:{
jumpToInfo(){
// 跳转到主页,使用$router.push()
this.$router.push({
path:'/user/'+this.$route.params.id+'/info/'
})
},
jumpToDetail(){
// 跳转到主页,使用$router.push()
this.$router.push({
path:'/user/'+this.$route.params.id+'/detail/'
})
}
},
created(){
this.user_id = this.$route.params.id;
//todo.
},
watch:{
'$route'(to,from){
this.user_id = to.params.id;
//todo.
}
}
}
const router = new VueRouter({
routes: [
{
path: '/user/:id', // 使用:id来接收id动态数据
name: 'User',
component: User,
// /user/:id下的子路由条目,/user/id/info和/user/id/detail
children: [
// 空路由,当请求/user/id/时,渲染UserHome
{
path: '',
component: UserHome
},
{
// 当 /user/:id/info 匹配成功,
// UserInfo 会被渲染在 User 的 <router-view> 中
path: 'info',
component: UserInfo
},
{
// 当 /user/:id/detail 匹配成功
// UserDetail 会被渲染在 User 的 <router-view> 中
path: 'detail',
component: UserDetail
}
]
}
]
})
let App = {
data() {
return {
user_page_name:"User",
// 假设有[1,2,3,4,5,6]用户
user_list:[,,,,,]
}
},
// 通过user_list中的user_id,使用v-for循环生成用户切换页签,点击页签按钮,使用编程式导航切换组件
template: `
<div>
<div class="header">
<button v-for="id in user_list" :key='id' @click="jumpToPage(id)">User{{id}}</button>
</div>
<router-view></router-view>
</div>
`,
methods:{
// 编程式导航切换组件
jumpToPage(id){
this.$router.push({
path:'/user/'+id
})
}
}
}
var vm = new Vue({
el: "#app",
data() {
return {}
},
router,
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
})
</script>
</body>
</html>
实现效果:
9.命名视图
前面我们使用的路由条目中,都是一个path对应一个component。
当我们在一个组件中可能会使用多个子组件时,例如在User组件中,不是UserInfo和UserDetail之间切换,而是同时显示两个子组件,则需要命名视图:
const router = new VueRouter({
routes: [
{
path: '/user/:id', // 使用:id来接收id动态数据
name: 'User',
component: User,
// /user/:id下的子路由条目,/user/id/info和/user/id/detail
children: [
{
path: '',
components: {
default: UserHome,
}
},
{
// 当 /user/:id/info 匹配成功,
// UserInfo 会被渲染在 User 的 <router-view> 中
path: 'info_detail',
components: {
default: UserInfo,
detail: UserDetail
}
}
]
}
]
})
由于一个组件中要使用多个子组件,则除了一个默认的default子组件,其余都要指定名称。注意,这里是components,而不是component。
既然路由中都指定了多个子组件,那么在使用的地方也要有多个<router-link>标签:
const User = {
data() {
return {
user_id: ''
}
},
// 在User页面中提供一个按钮,点击跳转到Home主页
template: `
<div class="content" style="background-color: darkgoldenrod">
我是User-{{user_id}}页面
<button @click="jumpToInfoDetial">跳转到Info_Detail</button>
<br>
<router-view></router-view>
<router-view name="detail"></router-view>
</div>
`,
methods: {
jumpToInfoDetial() {
// 跳转到主页,使用$router.push()
this.$router.push({
path: '/user/' + this.$route.params.id + '/info_detail/'
})
}
},
created() {
this.user_id = this.$route.params.id;
//todo.
},
watch: {
'$route'(to, from) {
this.user_id = to.params.id;
//todo.
}
}
}
除了default的子组件不用执行name属性,其余都要指定name属性。
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>命名视图</title>
<style>
.content {
width: 400px;
height: 200px;
} .header_button {
width: 30px;
height: 20px;
line-height: 20px;
background-color: brown;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script src="./static/vue-router.js"></script>
<script>
// User组件下的使用的子组件UserHome,用于请求/user/id/时
const UserHome = {
data() {
return {}
},
template: `
<div class="content" style="background-color: yellow">
我是UserHome页面
</div>
`
}
// User组件下的使用的子组件UserInfo,用于请求/user/id/info/时
const UserInfo = {
data() {
return {}
},
template: `
<div class="content" style="background-color: darkorange">
我是UserInfo子页面
</div>
`
}
// User组件下的使用的子组件UserDetail,用于请求/user/id/detail/时
const UserDetail = {
data() {
return {}
},
template: `
<div class="content" style="background-color: darkorange">
我是UserDetail子页面
</div>
`
}
// User组件
const User = {
data() {
return {
user_id: ''
}
},
// 在User页面中提供一个按钮,点击跳转到Home主页
template: `
<div class="content" style="background-color: darkgoldenrod">
我是User-{{user_id}}页面
<button @click="jumpToInfoDetial">跳转到Info_Detail</button>
<br>
<router-view></router-view>
<router-view name="detail"></router-view>
</div>
`,
methods: {
jumpToInfoDetial() {
// 跳转到主页,使用$router.push()
this.$router.push({
path: '/user/' + this.$route.params.id + '/info_detail/'
})
}
},
created() {
this.user_id = this.$route.params.id;
//todo.
},
watch: {
'$route'(to, from) {
this.user_id = to.params.id;
//todo.
}
}
}
const router = new VueRouter({
routes: [
{
path: '/user/:id', // 使用:id来接收id动态数据
name: 'User',
component: User,
// /user/:id下的子路由条目,/user/id/info和/user/id/detail
children: [
{
path: '',
components: {
default: UserHome
}
},
{
// 当 /user/:id/info 匹配成功,
// UserInfo 会被渲染在 User 的 <router-view> 中
path: 'info_detail',
components: {
default: UserInfo,
detail: UserDetail
}
} ]
}
]
})
let App = {
data() {
return {
user_page_name: "User",
// 假设有[1,2,3,4,5,6]用户
user_list: [1, 2, 3, 4, 5, 6]
}
},
// 通过user_list中的user_id,使用v-for循环生成用户切换页签,点击页签按钮,使用编程式导航切换组件
template: `
<div>
<div class="header">
<button v-for="(id,index) in user_list" :id="id" @click="jumpToPage">User{{id}}</button>
</div>
<router-view></router-view>
</div>
`,
methods: {
// 编程式导航切换组件
jumpToPage(e) {
this.$router.push({
path: '/user/' + e.target.getAttribute('id')
})
}
}
}
var vm = new Vue({
el: "#app",
data() {
return {}
},
router,
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
})
</script>
</body>
</html>
实现效果:
可以看到,我们点击"切换到Info_detail"按钮是,同时显示了UserInfo和UserDetail组件。
三、获取原生DOM
1.使用this.$refs获取标签或组件
我们之前在vue中获取原生的DOM,都是利用document来获取的,不是很方便。
vue为我们提供了ref属性,来方便的获取标签。
<div ref='div1'></div>
<p ref='p1'></p>
<Home ref='home1'></Home>
获取标签:
this.$refs.div1 //获取ref为'div1'的<div>标签
this.$refs.p1 //获取ref为'p1'的<p>标签
this.$refs.home1 //获取ref为'home1'的组件对象
注意this.$refs只能获取当前组件的template中的标签或组件(标识了ref属性的),例如当前组件为App组件,App组件在template中使用了User组件:
template: `
<div>
<div ref="div1"></div>
<p ref="p1"></p>
<User ref="user_component"></User>
</div>
`,
此时,我们使用this.$refs只能获取到这个template中的div、p和User组件。因为this代表的是App组件。
假设User组件的template中使用了子组件UserInfo和UserDetail,则不能在App组件中使用this.$refs获取,而要按层级来获取:
// 在App组件中
this.$refs.user_component.$refs.userinfo_component
2.组件的成员属性
在任何一个组件中,例如App组件,我们可以使用this获取他的所有成员属性,例如:
this.$refs // 获取当前组件template中存在ref属性的标签和组件的列表
this.$root // 获取vue根实例
this.$parent // 获取该组件的父组件
this.$children // 获取该组件的子组件列表
...
更多属性可以打印this来查看。
☜(ˆ▽ˆ)