从头学vue-router

引言

首先用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增加开销

总结

从里到外

  1. 组件内的守卫:离开该组件,更新该组件,进入该组件时触发
  2. 路由独享的守卫:进入该路由时触发
  3. 全局守卫:每一次路由的进入,解析,结束触发

触发顺序场景:
从路由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信息,展示不同页面布局),响应式布局。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值