单页应用:一个页面即一个简单应用,组件化开发
传统的页面调转是浏览器请求新页面,单页是把原本的多个页面以组件的形式集成在一个页面中,页面跳转时由vue路由到目标页面,因为目标页面是当前页面的一部分,切换到指定部分即可,浏览器不用发起新请求。
单页、路由的基本使用
#下载vue的路由插件
npm install vue-router
<div id="app"></div>
<!-- 引入vue.js -->
<script src="js/vue.js"></script>
<!-- 引入vue的路由插件。上线时均要换为min版 -->
<script src="js/vue-router.js"></script>
<script>
// 首页
var Index={
template:`
<div>
<p>this is the index page</p>
<p><a href="#/login">login</a></p> <!-- 注意url写法,#/开头 -->
<p><a href="#/register">register</a></p>
</div>
`,
}
// 登录页面
var Login={
template:`
<div>
<p>this is the login page</p>
<p><a href="#/index">index</a></p>
<p><a href="#/register">register</a></p>
</div>
`
}
// 注册页面
var Register={
template:`
<div>
<p>this is the register page</p>
<p><a href="#/index">index</a></p>
<p><a href="#/login">login</a></p>
</div>
`
}
// 启用vue路由
Vue.use(VueRouter);
// 创建路由对象
var router=new VueRouter({ //参数是对象
routes:[ //配置路由规则,对象数组形式
{path:'',name:'index',component:Index}, //path指定映射地址,注意不带#,component指定组件。path=""表示该页面组件是默认显示的页面
{path:'/login',name:'login',component:Login},
{path:'/register',name:'register',component:Register},
]
});
new Vue({
el: '#app',
router: router, //设置路由对象。因为我们使用的路由对象和key名称相同,也可以像html属性一样简写为一个:router
template:`
<div>
<!--路由页面只是当前页面的一部分,当前页面可以写一些其他内容,写的内容是所有路由页面-->
<p>this is common area</p> <!-- 公共部分,页面都有的部分 -->
<router-view></router-view> <!-- 目标页面。路由到哪个页面,就用对应的组件替换此元素 -->
</div>
`,
})
</script>
运行,假设这个html页面的地址是:xxx.html#/
则首页的地址是:xxx.html#/
登录页面的地址是:xxx.html#/login
注册页面的地址是:xxx.html#/register
表面上是3个页面,但实际上只有xxx.html一个页面,这3个页面都在xxx.html中。<router-view>是一个容器,用来容纳目标页面。
一个组件即一个页面,组件往往很大,通常把每个组件写在单独的js文件中,在xxx.html中使用<script src="">引入
路由模式
有2种路由模式
1、哈希模式(默认)
监听地址栏url的hashchange事件,url改变时获取新url的哈希值,与VueRouter对象中配置的路由规则比较,找到新url对应的组件,用对应的组件替换<router-view>。(hash模式的原理)
2、history模式
需要后台配合,把接口都打到前端打包好的单页上(xxx.html)
路由跳转
实现路由跳转有3种方式
- <a>链接
- <router-link>标签
- $router对象
<a href="#/login">login</a> <!-- 要带# -->
<router-link to="/login">login</router-link> <!-- 不带# -->
<script>
new Vue({
el: "#app",
router,
template: `
<div>
<button @click="goLogin">login</button>
<router-view></router-view>
</div>
`,
methods: {
goLogin() {
this.$router.push({ path: "/login" }); //不带#
},
},
});
</script>
<router-link>、$router都是vue路由中的,都用路由了,url中会自动加#号,我们写路径的时候不需要加#号。
<a>是html的标签,url中不会自动加#号,需要我们自己加#号。
$router常用的方法
- push() 跳转到指定页面,会往history中插入记录
- replace() 和push()差不多,只是不会往history中插入记录
- go(-1) 跳转到history中的上一条记录,相当于点击浏览器的后退箭头
- forward(1) 跳转到history中的下一条记录,相当于点击浏览器的前进箭头
$router有个兄弟对象$route,$route常用的属性:path、query、params
在html中,<router-link>用得多;在js代码中,用$router
路由传参
<a>链接方式
<a href="#/xxx?username=chy&age=20">index</a> <!-- 传参 -->
<script>
// 页面组件
var Xxx={
template:`
<div>
<p>{{this.$route.query.username}} {{this.$route.query.age}}</p>
<p>{{username}} {{age}}</p>
</div>
`,
data(){
return{
username:'',
age:''
}
},
created(){ //路由到此组件|页面时,会新建此组件的实例,只能在created()中获取传递的数据
this.username=this.$route.query.username; //query是查询字符串对象,<a>链接传递的参数只能用query来取
this.age=this.$route.query.age;
}
}
</script>
如果只在创建组件时使用一次,直接用组件的$route.query取值即可;如果后续还要使用,则需要用变量来保存参数
<router-link>、$router对象方式
都可以用query或者params传递参数
<!-- query,get请求 -->
<router-link :to="{name:'xxx',query:{username:'chy',age:20}}">xxx</router-link>
<!-- params,post请求 -->
<router-link :to="{name:'xxx',params:{username:'chy',age:20}}">xxx</router-link>
// $router对象方式,query、params
this.$router.push({name:'xxx',query:{username:'chy',age:20}});
this.$router.push({name:'xxx',params:{username:'chy',age:20}});
都可以用路由配置中的name或者path指定目标组件。
取值方式和<a>差不多,$route.query、$route.params,什么方式传就用什么方式取
如果使用params方式,路由配置的url可以写成restful风格
var router = new VueRouter({
routes: [
{path: "/xxx/:username", name: "xxx", component: Xxx}, //:参数名 可以获取指定参数的值
],
});
如果url写成restful风格,path就不是固定的,传递参数时只能用name指定目标组件,不能用path
常使用ES6的解构赋值处理参数
let {username,password}=req.query;
路由传参 参数不刷新的问题
以上传参方式都存在一个问题:多次传参,如果直接使用参数,可以获取到本次的参数;如果先把参数赋给变量,使用变量,则变量的值永远是第一次传递的参数值,即参数不刷新。
原因:把变量赋值写在created()中,这个钩子函数只在组件创建时执行1次,vue路由会复用组件,后续再向该组件传递参数时,不会调用重新创建该组件,也就不会执行created()重新赋值。
如果参数只在页面加载时读取一次,可以在create()中直接取,后续还要使用这个值的话,可以用变量来保存;如果要多次获取不同的路由参数,后续的路由参数不会刷新,如何解决参数不刷新问题?2种方案
1、使用 :key 标识路由跳转
<router-view :key="$route.fullPath"></router-view>
fullPath是完整的url,如果是query方式传参,fullPath中包含了参数;如果是params方式传参,参数写成restful风格(用一个唯一标识参数表的字段,比如username、uid),fullPath中也包含了参数。
参数不同时 => $route.fullPath不同 => key不同,key不同时会重新创建组件,执行created()重新获取参数给变量赋值。
核心思路是:参数不同时key也要不同,key不同就会重新创建组件。key不一定要用$route.fullPath,常用的还有
$route.query|params.username|uid //能唯一标识参数表的参数
new Date().getTime() //时间戳(毫秒级)
此种方式,参数不同时会重新创建组件,往往会造成组件的频繁创建、销毁,开销很大,不建议使用。
2、使用数据监听(推荐)
在目标组件中监听$route.query或者$route.params,在数据监听中进行赋值,不需要使用created()
watch:{ //监听$route.query|params,当然直接监听$route也行
'$route.params'(){ //有.号,要引起来,不然识别不了
this.username=this.$route.params.username; //参数变化时就重新获取参数
this.age=this.$route.params.age;
}
},
路由到同一个页面时会复用组件,不重新创建组件,开销小,推荐。
最好是抽象出一个处理参数的方法,初次路由到该页面时在create()中调用1次,后续再次路由到该页面时watch中调用。需要注意监听的是$route.query|params,只要其中某个参数有变化,就会触发事件。
嵌套路由
路由配置中嵌套其它的路由配置
<div id="app"></div>
<script>
// 导航
var Nav={
template:`
<div>
<router-link :to="{name:'blog.index'}">首页</router-link>
<router-link :to="{name:'blog.article'}">文章</router-link>
<router-link :to="{name:'blog.idea'}">想法</router-link>
<router-link :to="{name:'blog.message'}">留言</router-link>
<router-view></router-view> <!-- 留坑,点击上面4个路由链接,会在此处显示对应的页面 -->
</div>
`,
}
// 首页
var Index={
template:`
<div>
<p>这是首页部分</p>
</div>
`,
}
// 文章
var Article={
template:`
<div>
<p>这是文章部分</p>
</div>
`,
}
// 想法
var Idea={
template:`
<div>
<p>这是想法部分</p>
</div>
`,
}
// 留言
var Message={
template:`
<div>
<p>这是留言部分</p>
</div>
`,
}
// 启用路由
Vue.use(VueRouter);
// 创建路由对象
var router=new VueRouter({
routes:[
{path:'/',name:'blog',component:Nav,children:[ //path直接映射到根路径(当前html页面下)下,children设置要嵌套的路由
{path:'',name:'blog.index',component:Index}, //默认页面
{path:'/article',name:'blog.article',component:Article}, //嵌套路由的name一般要带上外部的路由容器,一看就知道关系
{path:'/idea',name:'blog.idea',component:Idea},
{path:'/message',name:'blog.message',component:Message},
]},
]
});
new Vue({
el:'#app',
router, //使用路由
template:`
<div>
<router-view></router-view> <!-- 留给nav的坑位 -->
</div>
`,
})
</script>
嵌套路由的path可以映射到根路径下,也可以使用相对路径,相对于外部路由
{path:'/blog',name:'blog',component:Nav,children:[ //xxx.html#/blog
{path:'',name:'blog.index',component:Index}, //默认页面
{path:'/article',name:'blog.article',component:Article}, // /开头是映射到根路径下,xxx.html#/article
{path:'idea',name:'blog.idea',component:Idea}, // 相对于外部路由,xxx.html#/blog/idea
{path:'message',name:'blog.message',component:Message},
]},
路由守卫
一个页面路由到另一个页面,路由到目标页面之前可以用路由守卫做一些处理,比如登录检测、权限检查,不满足就给出提示信息,不路由到目标页面。
现在的项目基本都是mvc模式,登录检测、权限检查等请求预处理在后台已经做了,前端没必要用路由守卫,此处简单提一下
//写在html(单页容器)的Vue对象的mounted()中
mounted(){
router.beforeEach( (to,from)=>{ //路由守卫,校验每一个路由跳转。此处使用es6的箭头函数,参数:目标页面、来源页面
//......
});
},