引言
首先用vue-cli脚手架创建一个干净的vue项目,具体如何创建看官网。然后vue的单组件与路由中我分析了项目的结构
多页应用和单页应用
每一次页面跳转的时候,服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。
打开vue.js官网,查看network,发现第一条是请求一个html文件,点击’教程‘,又返回一个html。
为什么搜索引擎优化效果好(SEO)?
搜索引擎在做网页排名的时候,要根据网页内容才能给网页权重,来进行网页的排名。搜索引擎是可以识别html内容的,而我们每个页面所有的内容都放在Html中,所以这种多页应用,seo排名效果好。
但是它也有缺点,就是切换慢
因为每次跳转都需要发出一个http请求,如果网络比较慢,在页面之间来回跳转时,就会发现明显的卡顿。
我们用Vue写的项目是单页应用,比如我自己的项目。切换的时候不会再次请求html文件。
怎么做到不请求html页面却跟着变呢?
js会感知到url的变化,可以用js动态地把上一个页面的内容清除掉,再把下一个页面的内容挂载到页面上。
为什么页面切换快?
页面每次切换跳转时,并不需要做html文件的请求,这样就节约了很多http发送时延,我们在切换页面的时候速度很快。
缺点:首屏时间慢,SEO差
单页应用的首屏时间慢,首屏时需要请求一次html,同时还要发送一次js请求,两次请求回来了,首屏才会展示出来。相对于多页应用,首屏时间慢。
SEO效果差,因为搜索引擎只认识html里的内容,不认识js的内容,而单页应用的内容都是靠js渲染生成出来的,搜索引擎不识别这部分内容,也就不会给一个好的排名,会导致单页应用做出来的网页在百度和谷歌上的排名差。
有这些缺点,为什么还要使用Vue呢?
Vue还提供了一些其它的技术来解决这些缺点,比如说服务器端渲染技术,通过这些技术可以完美解决这些缺点,解决完这些问题,实际上单页面应用对于前端来说是非常完美的页面开发解决方案。
了解router
为了了解router,我在Home.vue组件的Mounted钩子函数里打印出来了console.log(this.$route)
得到结果如下:代表当前路由下有以下几个属性
先看params和query(负责动态传参),这俩现在都是一个空对象。里面是可以有键值对。
比如params:{name:‘wangyue’}
query:{age:‘23’,gender:‘female’}
vue中路由中需要传递参数的话可以用query和param传递,两者类似于get和post。前者地址栏类似xx?p=1后者为xx/1,且后者可以隐藏地址栏显示(后面会说)
这两个对象里的值都可以写入/获取
动态传参
params
1. 准备
先在router下的index.js里加
{//路径参数表示值必须要有但是值是随机的
//如果是XX/user/wangyue =>this.$route.params:{name:'wangyue'}
path: '/user/:name',//表示/user这个路由后面可以跟params:{name:'wangyue'}
name: 'User',
component: User
}
2. 写入:
2.1 router-link
在Home.vue父组件下跳转写法,router-link,一种方式是直接写路径参数,另一中是以对象格式来写
- < router-link to="/user/wangyue">用户1< /router-link>
这里就等于给params写入值:{name:‘wangyue’}- 或者以对象方式来写入
< router-link :to="{name:‘User’,params:{name:‘xiaoming’}}">用户4< /router-link>
【注意】:以对象格式来写必须要给路由命名,写name:‘User’,不可以用path
<template>
<div>
<div>home</div>
<span>动态路由传参 router-link测试:</span>
<router-link to="/user/wangyue">用户1</router-link>
<router-link to="/user/wanghuahua">用户2</router-link>
<router-link to="/user/sudaqiang">用户3</router-link>
<div style="height: 20px;width: 100px"></div>
<span>或者以对象格式来写:</span><router-link :to="{name:'User',params:{name:'xiaoming'}}">用户4</router-link>
<div style="height: 20px;width: 100px"></div>
<button @click="changeUser">push转路由</button>
</div>
显示效果如下
2.2 编程方式写入
用到了this.$router.push({})
给button加一个方法,一旦点击button,跳转路由
changeUser:function () {
this.$router.push({name:'User',params:{name:'wangyue'}})
}
3. 获取
若想,取出路由里的params,则用 {{$route.params.XX}}
<template>
<div>
<span style="display: inline-block">user:{{$route.params.name}}</span>
<!--<span style="display: inline-block;"> 我今年:{{$route.query.age}}岁了</span>-->
</div>
</template>
点击用户1router-link该跳转页面效果如下
在< script>中获取要加this 如this.$route.params.XX
query方式
就是我们看见很多网站后面问号传参,这个就不用再index.js里设置了
1. 写入
query的写入只可以用编程式,不可以用router-link
给button点击方法加一行代码
changeUser:function () {
this.$router.push({name:'User',params:{name:'wangyue'}})
this.$router.push({name:'User',query:{age:'23'}})//query写入
}
query写入可以用path,params只能用name
2. 读取
this.$route.query.XX(取Key为XX的值)
<template>
<div>
<span style="display: inline-block">user:{{$route.params.name}}</span>
<span style="display: inline-block;" v-if="$route.query.age"> 我今年:{{$route.query.age}}岁了</span>
</div>
</template>
显示如下
params注意
之前说到在index.js对route的配置里要对params写死,如 path: ‘/user:name’
是因为 < router-link to="/user/wangyue">用户1< /router-link>这种写入方式只有值‘wangyue’没有键’name’,所以在route配置时写死
如果以对象 < router-link :to="{name:‘User’,params:{name:‘xiaoming’}}">用户4< /router-link>或push写入方式,则无需path: ‘/user:name’,只配置path: '/user’即可,读取的时候直接$router.params.name即可
但这样,params里的值就不会显示在url地址栏上,如下用户4
<template>
<div>
<div>home</div>
<span>动态路由传参 router-link测试:</span>
<router-link to="/user/wangyue">用户1</router-link>
<router-link to="/user/wanghuahua">用户2</router-link>
<router-link to="/user/sudaqiang">用户3</router-link>
<div style="height: 20px;width: 100px"></div>
<span>或者以对象格式来写:</span><router-link :to="{name:'User',params:{name:'xiaoming',gender:'female'}}">用户4</router-link>
<div style="height: 20px;width: 100px"></div>
<button @click="changeUser">push转路由</button>
<router-link to="/login">登陆</router-link>
</div>
</template>
index.js中对user路由配置如下
{ path: '/user',
name: 'User',
component: User,}
对User.vue设置如下
<template>
<div>
<span style="display: inline-block">user:{{$route.params.name}}{{$route.params.gender}}</span>
<router-link :to="'/user/'+$route.params.name+'/more'">用户信息</router-link>
<router-view></router-view>
</div>
</template>
点击’用户四‘,user页面显示如下
可以看出params里的值能被读取,但不在url地址栏显示,这叫隐藏参数。router配置里的path:’/user’是设置路由显示。
隐藏参数刷新会丢失数据
非隐藏参数刷新不会丢失数据
f5刷新一下user页面,发现没有了,数据丢失了,可以通过localstorage解决刷新问题
改成path:’/user/:name/:gender’则为非隐藏参数,F5刷新不丢失数据
其他编程式导航 route的方法
刚才说了一个push(),还有其他的方法
router.replace(…)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录
就是返回的时候不会返回到上一个
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
//刷新页面
router.go(0)
实例测试:
在login页面加俩按钮
<template>
<div>
<div>登陆</div>
<div>
<button @click="goOn">go(1)</button>
<button @click="goBack">go(-1)</button>
</div>
</div>
</template>
<script>
export default {
name: "Login",
methods:{
goOn:function () {
this.$router.go(1)
} ,
goBack:function () {
this.$router.go(-1)
}
},
}
</script>
<style scoped>
</style>
点击登陆
点击go(-1),发现跳转回去了,我改成go(-100),无反应
因为没有下一步,所以go(1)无反应
重定向与别名
routes: [
{
path: '/',
redirect:'/home',//这里就是用户访问根页面,直接跳转到/home,实现重定向
},
{
path: '/home',
name: 'Home',
component: Home
}]
当我访问/,会自动跳转至/home
当用户在随便输入时,加一个默认路由
{path:'*',redirect:'/home'},
无论用户怎么随便输入都默认重定向到/home
别名:给/a取个别名/b,访问/b显示的是/a的内容,但URL仍是/b
routes: [
{
path: '/',
redirect:'/home',
},
{
path: '/home',
name: 'Home',
component: Home,
alias:'/123'
}]
当我输入/123,跳转至/home,但地址栏不变还是/123
导航钩子
全局守卫
用来监测所有的路由
- beforeEach&beforeResolve&afterEach
有点像vue的生命周期钩子函数,页面跳转时触发,为了了解这三个导航钩子,我在main.js中打印出,修改main.js如下
import Vue from 'vue'
import App from './App'
import router from './router'
import './assets/styles/meyer-reset.css'
import './assets/styles/border.css'//1像素
import fastClick from 'fastclick'//移动端300ms延迟问题解决
Vue.config.productionTip = false
fastClick.attach(document.body)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
});
router.beforeEach((to,from,next)=>{
console.log('before each involed',from,to)
next()
});
router.beforeResolve((to,from,next)=>{
console.log('before resolve invoked')
next();
})
router.afterEach((to,from)=>{
console.log('after each invoked')
})
原本页面如下:
点击‘用户一’
这三个钩子函数依次被触发。通过打印出来的内容可以看出,to是目的路由,from是来自哪个路由。据此,就可以利用钩子函数做一些修改,比如只有去login页面才跳转
router.beforeEach((to,from,next)=>{
console.log('before each involed',from,to)
if(to.fullPath=='/login') {
next()
}
else {
}
});
会发现只有点击登陆页面才跳转
点击其他时
不跳转,且只触发到beforeEach
稍作修改,可以改成某些页面只有用户登陆才可以查看,比如我想查看用户1/2/3/4,不允许查看,直接跳到登陆界面让登陆
router.beforeEach((to,from,next)=>{
console.log('before each invoked',from,to)
if(to.fullPath!=='/login'&&to.fullPath!=='/home') {
next('/login')
}
else {
next()
}
});
当我点击用户一
跳到了login,这个next里面可以是路径也可以是一个object,比如{name:‘login’},还可以加上params,query
以上是路由的全局钩子
路由独享守卫
就是将路由钩子函数写在我们的某个具体路由对象里面
- beforeEnter
我们在路由的配置里面也可以加钩子
修改下index.js
{
path: '/Login',
name: 'Login',
component: Login,
beforeEnter:function (to,from,next) {
console.log('before enter invoked')
next();
}
},
当点击登陆时,控制台输出如下
可以看出顺序在beforeEach和beforeResolve之间。因为在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫被调用
组件内守卫
也可以在Login.vue组件里面加钩子函数
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
<template>
<div>
登陆
</div>
</template>
<script>
export default {
name: "Login",
beforeRouteEnter(to,from,next) {
console.log('Login.vue:before route enter invoked');
next();
},
beforeRouteUpdate(to,from,next) {
console.log('Login.vue:before route update invoked');
next();
},
beforeRouteLeave(to,from,next) {
console.log('Login.vue:before route leave invoked');
next();
},
}
</script>
<style scoped>
</style>
点击登陆时的打印如下
放大
跳转回去
beforeRouteUpdate主要是在同一个组件下,不同的params被触发,为了验证我们修改下index.js中的user路径
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home/Home'
import List from '@/components/List/List'
import User from '@/components/User/User'
import More from '@/components/User/More'
import Login from '@/components/Home/Login'
import Post from '@/components/Home/Post'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect:'/home',
},
{
path: '/home',
name: 'Home',
component: Home,
alias:'/123'
},
{
path: '/list',
name: 'List',
component: List
},
{//路径参数表示值必须要有但是值是随机的
//如果是XX/user/wangyue =>this.$route.params:{name:'wangyue'}
path: '/user/:name',
name: 'User',
component: User,
beforeEnter:function (to,from,next) {
console.log('/user: before enter invoked')
next();
},
children:[
{
path:'more',
name:'More',
component:More
}
]
},
{
path: '/Login',
name: 'Login',
component: Login,
beforeEnter:function (to,from,next) {
console.log('/login:before enter invoked')
next();
}
},
{
path: '/Post',
name: 'Post',
component: Post
}
]
})
修改一下User.vue
<template>
<div>
<span style="display: inline-block">user:{{$route.params.name}}</span>
<span style="display: inline-block;" v-if="$route.query.age"> 我今年:{{$route.query.age}}岁了</span>
<router-link :to="'/user/'+$route.params.name+'/more'">用户信息</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "User",
beforeRouteEnter(to,from,next) {
console.log('User.vue:before route enter invoked');
next();
},
beforeRouteUpdate(to,from,next) {
console.log('User.vue:before route update invoked');
next();
},
beforeRouteLeave(to,from,next) {
console.log('User.vue:before route leave invoked');
next();
},
}
</script>
<style scoped>
</style>
当我点击‘用户一’时,先触发全局的beforeEach,然后是路由的beforeEnter,然后是组件内的beforeRouteEnter,再是全局的beforeResolve,全局的afterEach
直接在地址栏改成wanghuahua
可以看出触发了全局的beforeEach,再是组件内的beforeRouteUpdate,然后是全局的beforeResolve和全局afterEach
【用处】:可以在每次仅仅params有变化时获取,不用watch增加开销
总结
从里到外
- 组件内的守卫:离开该组件,更新该组件,进入该组件时触发
- 路由独享的守卫:进入该路由时触发
- 全局守卫:每一次路由的进入,解析,结束触发
触发顺序场景:
从路由A(组件a)到路由B(组件b):
a的beforeRouteLeave->beforeEach->B的beforeEnter->b的beforeRouteEnter->beforeResolve->afterEach
a组件内参数更新:
beforeEach->a组件的beforeRouteUpdate->beforeResolve->afterEach
注意
注意点:beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
meta元数据
路由元信息:定义路由的时候可以配置 meta 字段
【需求】:
- 访问user页面需要检测是否登陆->检测登陆信息->若已登陆则成功跳转,未登陆则跳转至login页面
- 访问其他页面无需检测是否登陆,直接跳转
所以给user路由加一个meta字段,requiresAuth 是自己起的字段名称,用来标记这个路由信息是否需要检测,true 表示要检测,false 表示不需要检测。
给index.js里的user路由设置meta字段,代表该路由是否需要检测后才能访问
{//路径参数表示值必须要有但是值是随机的
//如果是XX/user/wangyue =>this.$route.params:{name:'wangyue'}
path: '/user/:name',
name: 'User',
component: User,
beforeEnter:function (to,from,next) {
console.log('/user: before enter invoked')
next();
},
meta:{
requiresAuth: true//加一个自定义字段,需要检测
},
然后在全局前置守卫beforeEach里加判断,检查to这个对象是否拥有meta这个对象,如果有meta这个对象,检测它的meta对象是不是有requiresAuth这个属性,且为true
main.js里
let auth={
loggedIn(){//返回值为是否登陆
return false;
}
};
router.beforeEach((to,from,next)=>{
if (to.matched.some(record => record.meta.requiresAuth)) {//检验该对象是否有meta字段,meta字段里是否requiresAuth为true。满足代表该路由对象需要检测
if (!auth.loggedIn()) {//查看是否登陆,若未登陆,跳转到login
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {//已登陆,直接到to
next()
}
} else {//to路由对象无需检测,直接next
next()
}
});
解释下to.matched.some(record => record.meta.requiresAuth)
一个路由匹配到的所有路由记录会暴露为 $route 对象的 $route.matched 数组(嵌套路由会匹配多个路由记录)。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
some是ES6里数组的API,即遍历数组,return的值有一个为真则返回真
这里即遍历to.matched数组,record为该数组中的每一项,有一项record.meta.requiresAuth为真则返回真。则代表该路由对象需要检测是否登陆。
随后检测auth.loggIn()的返回值,为true则已登陆,可以跳转。为false则未登陆,重定向到login。
现在这里返回值设为false
我点击用户一
跳到了登陆页面,若把auth.loggIn()的返回值设未true
正常访问。也可以通过路由元信息meta实现不同路由展现不同页面(带不同的meta信息,展示不同页面布局),响应式布局。