vue router复习

229 阅读11分钟

vue route

路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫

路由守卫参数

image.png

this.$routethis.$router的区别,this.$router当前路由器,this.$route当前路由对象, $router是指整个路由实例,你可以操控整个路由,通过'$router.push'往其中添加任意的路由对象,routes:指router路由实例的routes API.用来配置多个route路由对象.

  • $route:是指当前路由实例('$router')跳转到的路由对象;
  • 路由实例可以包含多个路由对象.它们是父子包含关系

router.js中,path跳转路径,就是url上显示的,component跳转渲染后看到的组件,router-view页面因为路由跳转而需要渲染的视图区域,渲染的就是router中component对应的组件

路由模式

hash:把 app 当做视图渲染容器,当切换路由的时候触发视图容器的更新,这其实就是大多数前端框架哈希路由的实现原理。,即通过在链接后添加 # + 路由名字,根据匹配这个字段的变化,触发 hashchange 事件,动态的渲染出页面。就有点类似像 a 链接用作页面上的锚点一样,不会刷新页面。,但是兼容性棒棒,比如www.test.com/#/就是 Hash URL,当#后面的哈希值发生变化时,可以通过hashchange事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是www.test.com。Hash 模式相对来说更简单,并且兼容性也更好。每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。,

history:去除‘#’符号,让url变好看,且还包括back、forward、go三个方法,对应浏览器的前进、后退、跳转操作,就是浏览器左上角的前进后退等按钮进行的操,美化后的hash模式,路径中会去掉#。依赖于html5的history,pushState API,所以要担心IE9及以下的版本,History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,下面会讲服务端配置。

history 模式要玩好,还需要后端配置支持。hash模式不需要配置,因为浏览器会忽略#和?后面的参数

因为我们的应用是个单页客户端应用,仅有一个入口文件就是index.html。

浏览器直接访问oursite.com/index.html可…

当时发生了下面两种情况,后端会因为url不匹配而报错:

客户ctrl + F5刷新页面,或者地址栏按enter键重新加载页面,恰好此时页面的url不是 oursite.com/index.html 客户不是通过oursite.com/index.html访… 因为对浏览器和后端来说,history模式的url就是正常的url,后端会根据匹配规则匹配相应的api接口或者静态资源。因此需要在后端添加一个匹配所有情况的配置,如果url匹配不到,就返回index.html页面。

abstract:非浏览器环境,会强制使用这个模式,例如weex

history模式服务端配置

我个人和公司都是用nginx,这里就讲nginx,配置这个的原因是当你进入某个路由之后,再次刷新页面时(或者是浏览器直接输入某个路由路径时) ,当刷新页面,浏览器就会重新dns解析,tcp协议,这个时候会根据浏览器的url去服务器找对应资源,当然我们vue-router是为单页面服务的,对应的url在服务端是肯定没有静态资源的,就会出现404,当配置了以下url重写语句,注意是重写,不是重定向,不改变url的情况重写浏览器内容,重写到index.html,因为这个index.html使我们项目的入口,index.html里面会读取当时打包好的app.js,就可以读取到路由配置,以实现我们浏览器的url对应的路由页面。

hash模式不需要配置,因为浏览器会忽略#和?后面的参数

区别:

1. hash路由在地址栏URL上有#,而history路由没有会好看一点

2. 我们进行回车刷新操作,hash路由会加载到地址栏对应的页面,而history路由一般就404报错了(刷新是网络请求,没有后端准备时会报错)。

3. hash路由支持低版本的浏览器,而history路由是HTML5新增的API。

4. hash的特点在于它虽然出现在了URL中,但是不包括在http请求中,所以对于后端是没有一点影响的,所以改变hash不会重新加载页面,所以这也是单页面应用的必备。

5. history运用了浏览器的历史记录栈,之前有back,forward,go方法,之后在HTML5中新增了pushState()和replaceState()方法(需要特定浏览器的支持),它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的URL,但是浏览器不会马上向后端发送请求。

路由属性对象

为了下面理解的方便这里简单介绍下常用的路由对象属性,在组件内可以通过this.$route(不是$router!)进行访问.

$route.path

类型: string 字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"

$route.params

类型: Object 一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。

$route.query

类型: Object 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user = 1,如果没有查询参数,则是个空对象。

$route .name

当前路由的名称,如果有的话。这里建议最好给每个路由对象命名,方便以后编程式导航.不过记住name必须唯一!

$route.hash

类型: string 当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

$route.fullPath

类型: string 完成解析后的 URL,包含查询参数和 hash 的完整路径。

$route.matched

类型: Array<RouteRecord> 一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。 $route.redirectedFrom 如果存在重定向,即为重定向来源的路由的名字。

route的params 和query的区别

const routes = [ 
{ name: 'users', path: '/users/:id', component: Users },
{ path: '/books', component: Books } 
] 
// params使用场景 
router.push('/users/123') // 跳转时 
router.push({ // 另一种方式跳转 
name: 'users',
params: { id: 123 } 
}) 
// 获取id route.params.id // 123 
// 实际URL地址 => localhost:8080/users 
// query使用场景 
router.push('books?q=123') // 跳转时 
router.push({ // 另一种方式跳转
path: '/books',
query: { q: 123 } 
}) 
// 获取query route.query.q // 123 
// 实际URL地址 => localhost:8080/books?q=123
//  params参数都不会显示在url地址栏中**.除了在路由中通过routes进行配置的.所以用户**刷新页面后,params参数就会丢失!**
**query参数可以正常显示在url地址栏中**.刷新页面后也不会丢失

注意,path和params不能一起用

声明式导航

1.定义: 声明式导航用router-link配合to属性实现点击切换路由

2.router-link提供了声明式导航高亮的功能(自带类名)

3.声明式导航的基本使用

在app.vue的template模板里面通过router-link配合to属性跳转到相应的页面

语法格式: xxx

声明式导航传参

第一种

1.传值在app.vue里传
// 传值的语法格式: to='/path路由路径/?参数名=值'
<router-link to='/find/?name=鸣人'>发现音乐</router-link>
 
2.接收值要在你跳转的find.vue页面拿
// 接收值的语法格式: {{$route.query.参数名}}
在find.vue组件里使用{{$route.query.name}}获取值

第二种

<router-link :to="{ name: 'news', params: { userId: 1111}}">click</router-link>

<router-link :to="{ path: '/news', query: { userId: 1111}}">click</router-link>

编程式导航

this.router.push编程式导航,跟routerlink功能一模一样this.router.push 编程式导航,跟router-link 功能一模一样 this.router.replace 跟push差不多,只是history中是直接取代,没法回退,push可以回退下一步

路由router.js中,children里面的路由path前面会加上副路由的路径,比如父路由 path: '/page',,子路由path:'foo',子路由的path其实是 '/page/foo',如果子路由path前面家里/会被当成根路径,跳转失败

回退

开发中遇到返回上一页的需求,有两个方法可选:

1.$router.back()

2.$router.go()

在使用中发现,使用router.back()router.back()和router.go(-1)作用相同,都是返回原页面。但如果原页面路由携带参数,使用以上两个方法返回的原页面路由参数消失,此时使用$router.back(-1)返回原页面路由参数仍存在。

多视图

配合router-view使用

 <h1>Named Views</h1>
 <p>
   <router-link to="/avenger">复仇者联盟</router-link>
 </p>
 <router-view ></router-view>
 <router-view name="ironMan"></router-view>
 <router-view name="captainAmerica"></router-view>
</div>
<!--这里我们给其中两个视图命名为ironMan和captainAmerica;没有设置name的视图,会获得默认命名为default>

const router = new VueRouter({
  routes: [
    { path: '/avenger', name:'avenger',components: {
        default: stanLee,
        ironMan: ironMan,
        captainAmerica: captainAmerica
      }
	//如果有多个视图需要展示时,以前的component换成components(加上s!!),写成对象形式.左边的ironMan指的就是<router-view>里设置的name="ironMan";右边的则指的是下面的组件ironMan.
    }
  ]
})

const stanLee = { template: '<div>斯坦李</div>' }
const ironMan = { template: '<div>钢铁侠</div>' }
const captainAmerica = { template: '<div>美国队长</div>' }

任何子路由都是在其父路由的组件中切换显示,不管是多少层的路由嵌套,都是这样的理解,所以父路由需要有以下两点,二者缺一不可有组件引用,组件中有router-view组件,router-view就是用来显示子路由的

重定向 redirect 可以写 :'/home' 也可以 :{name:'home} 还可以 :to=>{里面写判断}

别名 alias 相当于另一个名字,路由也可以通过props传参给组件,比如props:{name:'abc}

路由懒加载

webpack模式 component: resolve=>require(['../home',resolve])

es 模式 component: ()=>import('../home')

路由导航

全局前置守卫,router.beforeEach 全局解析守卫 roter.beforeResolve在在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后 全局后置钩子 router.afterEach 不接受next函数,也不会改变导航本身

路由独享守卫 ,跟beforeEach用法一样
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave

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`
  }
}

image.png

元信息

在路由中存储自己定义的属性,比如

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
	
 //数组some方法,如果meta.requiresAuth为ture,
 则返回true.此时,说明进入该路由前需要判断用户是否已经登录 
    if (!auth.loggedIn()) {   
    //如果没登录,则跳转到登录页
      next({
        path: '/login',
        query: { redirect: to.fullPath }  
        //官方例子的这个小细节很好,通过query将要跳转的路由路径保存下来,
        待完成登录后,就可以直接获取该路径,直接跳转到登录前要去的路由
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

next()

解释1

beforeRouteLeave (to, from, next) {
        console.log('离开路路由')
       if(to.fullPath==='/home'){
         next();
       }else{
         next('/home')
       }

这个是组件路由,我想实现的效果是在这个页面点击浏览器的返回按钮后要返回 /home页面而不是上一个页面,上面的代码是没问题的,而我之前的写法就一直死循环

// 下面的写法会死循环
     beforeRouteLeave (to, from, next) {
        console.log('离开路路由')
       next('/home')

vue-router的next()方法无参和有参时是不一样的

当执行钩子函数时如果遇到next(’/home’)等时会中断当前导航, 比如当前导航是去/a,那么遇到next(’/home’)后就会把to.path改为/home,然后会重新触发这个离开的钩子, 注意:此时会重新触发执行这个钩子,而不是在这个钩子函数继续执行的

当重新触发后就会继续执行next(’/home’)所以会一直循环 。至于解决办法就是判断下,如果已经是/home了就next()。 解释2 为什么next()指定路径会出现死循环

router.beforeEach((to, from, next) => {
  console.log('beforeEach');
  if(true){
    next('/');
  }else{
    next();
 }
});

next()直接跳转到to.path路径,没有再执行一遍beforeEach导航钩子,next(’/’)或者next(’/login’)自己指定路径的,路由跳转的时候还执行一遍beforeEach导航钩子,所以上面出现死循环; 栗子:如我们登录页(’/login’)面进入首页(’/’),可以这么写:

router.beforeEach((to, from, next) => {
var userInfo= JSON. parse(sess ionStorage. getItem('userInfoStorage'));//获取浏览器缓存的用户信息
if(userInfo){//如果有就直接到首页咯
next() ;
}else{
if(to. path==' /login' ){//如果是登录页面路径,就直接next()
next() ;
}else{//不然就跳转到登录;
next(' /login');
}
}
});

问题 出现无限循环是因为之前我没有弄清楚next()流程 因为每次跳转到一个路由的时候都会 触发 全局守卫 由于判断条件未改变 所以 一直循环

解决方法

判断to路由的meta (isRequireAuthTrue)是否为true 判断是否登陆(isLogin)

// ('/')为登陆界面  
// next() 默认跳转to的path
if(isRequireAuthTrue){
    if(isLogin){
      console.log('导航守卫保护');
      next(); //成功则跳转
    }else {
      console.log('你还没有登陆!');
      next({path:'/'}) //失败则跳转到登陆页面
    }
  }else {
    next();//不需要导航守卫的则直接next跳转
  }

解释3 问题描述:在调用Vue中的全局前置导航守卫beforeEach(to, from, next)中的next函数,在给next()传参数的时候出现死循环的问题!导致问题原因:其实导致这个问题的根本是没有完全理解beforeEach()和next("/somewhere")的作用首先,我们来看看next()的用法

究其根本是当执行了next("/somewhere")的时候又触发了beforeEach()这个钩子,所以就变成了无限死循环!

解决办法:

router.beforeEach((to, from, next) => {
    let {path} = to;
    if(path=== "/somewhere") {
        next(); // 导航目的地符合预期,继续后续事情
     }else {
        next("/somewhere");  
        // 导航目的地不符合预期,重新路由新路径地址,然后会再次触发beforeEach钩子并进行二次判断
    }
});

解释4 页面跳墙中使用 vue-router中的 beforeEach的死循环问题

问题展现

import Router from 'vue-router'
const router = new Router({
    {path: '/', component: index },
    {path: '/login', component: login},
    {path: '/error', component: error},
    {path: '*', component: error}
})

router.beforeEach((to, from, next) => {
    const isLogin = sessionStorage.getItem('loginData')
    if (isLogin) {
        next()
    } else {
        next('/error')
    } 
})

最近在使用时,一直陷入死循环,当时的想法是如何将路由提取出来,脱离beforeEach的控制,之后发现不可行。上面问题再现,会出现死循环,因为 /error 会在进入前 又要进入beforeEach中 ,这样就会一直循环下去 所以就是想如何跳出这个循环即可

router.beforeEach((to, from, next) => {
    const isLogin = sessionStorage.getItem('loginData')
    if (isLogin) {
        next()
    } else {
        //next('/error')
        if (to.path === '/error') { //这就是跳出循环的关键
           next()
        } else {
            next('/error')
        }
    } 
})

这样写,其实这个会执行两次,第二次进来是以/error的路由进来的