24.Vue路由管理器:Router(进阶篇)

导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。其实,导航守卫就是路由跳转过程中的一些钩子函数,再直白点路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。

研究导航守卫,先做一个demo看看各种守卫的触发时机:

//main.js
//全局导航守卫
router.beforeEach((to, from, next) => {
  console.log("全局导航守卫:beforeEach()");
  next(()=>{
	  console.log("全局导航守卫:beforeEach()--->next()");
  });
}),
router.beforeResolve((to, from, next) => {
  console.log("全局导航守卫:beforeResolve()");
  next(()=>{
  	  console.log("全局导航守卫:beforeResolve()--->next()");
  });
}),
router.afterEach((to, from) => { //没有next()
  console.log("全局导航守卫:afterEach()");
}),

//router/index.js
{
    path: '/guard/:id',
    name: 'Guard',
    component: () => import('../views/Guard.vue'),
    //独享导航守卫
    beforeEnter: (to, from, next) => {
        console.log("独享导航守卫:beforeEnter()");
        next(() => {
            console.log("独享导航守卫:beforeEnter()--->next()");
        });
    }
},

//Guard.vue
<template>
	<div>路由守卫测试页面{{$route.params.id}}</div>
</template>

<script>
	export default {
		//组件导航守卫,不调用next()将导致导航终止
		beforeRouteEnter(to, from, next) {
			console.log("组件导航守卫:beforeRouteEnter()");
			next(() => {
				console.log("组件导航守卫:beforeRouteEnter()--->next()");
			});
		},
		beforeRouteUpdate(to, from, next) {
			console.log("组件导航守卫:beforeRouteUpdate()");
			next(() => {
				console.log("组件导航守卫:beforeRouteUpdate()--->next()");
			});
		},
		beforeRouteLeave(to, from, next) {
			console.log("组件导航守卫:beforeRouteLeave()");
			next(() => {
				console.log("组件导航守卫:beforeRouteLeave()--->next()");
			});
		},
		//组件生命周期
		beforeCreate() {
			console.log("组件生命周期:beforeCreate()");
		},
		created() {
			console.log("组件生命周期:created()");
		},
		beforeMount() {
			console.log("组件生命周期:beforeMount()");
		},
		mounted() {
			console.log("组件生命周期:mounted()");
		}
	}
</script>

运行:
在这里插入图片描述
 

全局守卫

全局守卫是指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数,包括beforeEach、beforeResolve(2.5+)、afterEach

  • beforeEach:在路由跳转前触发,参数包括to,from,next(参数会单独介绍)三个,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚。
  • beforeResolve:这个钩子和beforeEach类似,也是路由跳转前触发,参数也是to,from,next三个,和beforeEach区别官方解释为:是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。
  • afterEach:他是在路由跳转完成后触发,参数包括to,from,但不接受next,他发生在beforeEach和beforeResolve之后,beforeRouteEnter之前。
//main.js
router.beforeEach((to, from, next) => {
    //...
    next();
}),
router.beforeResolve((to, from, next) => {
    //...
    next();
}),
router.afterEach((to, from) => { 
    //...
    //没有next()
}),

 

独享守卫

是指在单个路由配置的时候也可以设置,只有一个钩子函数beforeEnter。
beforeEnter:和beforeEach完全相同,如果都设置则在beforeEach之后紧随执行,参数to、from、next。

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

 

组件守卫

是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。包括beforeRouteEnter、beforeRouteUpdate (2.2+)、beforeRouteLeave三个。

  • beforeRouteEnter:路由进入之前调用,参数包括to,from,next。该钩子在全局守卫beforeEach和独享守卫beforeEnter之后,全局beforeResolve和全局afterEach之前调用,要注意的是该守卫内访问不到组件的实例,也就是this为undefined,也就是他在beforeCreate生命周期前触发。在这个钩子函数中,可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
  • beforeRouteUpdate:在当前路由改变时,并且该组件被复用时调用,可以通过this访问实例。参数包括to,from,next。对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,组件实例会被复用,该守卫会被调用。当前路由query变更时,该守卫也会被调用。
  • beforeRouteLeave:导航离开该组件的对应路由时调用,可以访问组件实例this,参数包括to,from,next。通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
const Foo = {
  template: `...`,
  
  // 在渲染该组件的对应路由被 confirm 前调用
  beforeRouteEnter (to, from, next) {
    // 不能获取组件实例`this`,因为当守卫执行前,组件实例还没被创建
        next(vm => {
            // 通过 `vm` 访问组件实例
        })
  },
  
  //在当前路由改变,但是该组件被复用时调用
  beforeRouteUpdate (to, from, next) {
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  
  // 导航离开该组件的对应路由时调用
  beforeRouteLeave (to, from, next) {
    // 可以访问组件实例 `this`
    const answer = window.confirm('确定要离开?当前页面还未保存!')
    if (answer) {
        next()
    } else {
        next(false)
    }
  }
}

 

next

上面的守卫导航除afterEach都用到了next函数参数。一定要调用该方法来 resolve 守卫,否则守卫 resolve 完之前一直处于 等待中。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link标签 的to属性或 router.push() 中的选项。
  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保 next() 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。示例:

// 错误
router.beforeEach((to, from, next) => {
  // 如果用户未能验证身份,则 `next` 会被调用两次
  if (to.name !== 'Login' && !isAuthenticated) {
      next({ name: 'Login' });
  }
  next();
})

// 正确
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) {
      next({ name: 'Login' });
  }
  else{
      next();
  } 
})

 

数据获取

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时需要从服务器获取用户的数据。

导航完成后获取数据

在组件的 created()中获取数据,就能在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

<template>
  <div class="post">
    <div v-if="loading" class="loading">
      Loading...
    </div>

    <div v-if="error" class="error">
      {{ error }}
    </div>

    <div v-if="post" class="content">
      <h2>{{ post.title }}</h2>
      <p>{{ post.body }}</p>
    </div>
  </div>
</template>

<script>
    export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {
    // 组件创建完后从服务器获取数据,此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
}
</script>

 

在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。即 beforeRouteEnter 守卫中获取数据,当数据获取成功后调用 next 方法。

export default {
  data () {
    return {
      post: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了,set数据逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },
  methods: {
    setData (err, post) {
      if (err) {
        this.error = err.toString()
      } else {
        this.post = post
      }
    }
  }
}

 

路由元信息

定义路由的时候可以配置 meta 字段。然后通过$route.meta可以获取到。

// router/index.js
	{
		path: '/meta',
		name: 'Meta',
		component: () => import('../views/Meta.vue'),
		meta: { //父类添加了meta作为需要登陆的标志
			requiresAuth: true 
		},
		children: [{
			path: 'child',
			name: 'MetaChild',
			component: () => import('../views/MetaChild.vue'),
		}]
	}
 
 // main.js
 // 全局守卫
 router.beforeEach((to, from, next) => {
             //to.matched是一个数组,包含匹配到相关的 路由记录,路由记录可以是嵌套的,以上如果访问/meta/child会为['meta的路由信息','meta/child的路由信息']
		if (to.matched.some(record => record.meta.requiresAuth)) {
			console.log(to.name+"页面要登录的,重定向到home登录");
			next({
				path: '/',
			})
		} else {
			console.log(to.name+"页面不用登录");
			next();
		}
}),

运行:
在这里插入图片描述
 

滚动行为

当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时想要页面滚到某个位置可以使用此特性。

// router/index.js
let posY = 0; //保存滚动位置
const router = new VueRouter({
	routes: [...],
	scrollBehavior(to, from, savedPosition) {
             //savedPosition传入的是每次跳出页面时的偏移,要保存起来给下次浏览器前进/回退跳回来用
		if (from.name == 'Scroll' && (savedPosition)) {
			console.log("Scroll从页面跳出,位置被保存")
			posY = savedPosition.y;
		} else if (to.name == 'Scroll') {
			console.log("跳转到Scroll,位置被读取")
			return {
				x: 0,
				y: posY
			};
		} else {
			console.log("没有位置信息")
			return {
				x: 0,
				y: 0
			};
		}
	}
})



//vue
<template>
	<div>
		<ul>
			<li v-for="(item,index) in list" :key="index">{{index}}</li>
		</ul>
	</div>
</template>

<script>
	export default {
		data() {
			return {
				list: new Array(1000)
			}
		}
	}
</script>

运行,从Scroll.vue跳转首页后回退,即可看到之前保存的偏移:
在这里插入图片描述
 

过渡动画

vue Transition 的所有动画功能都可以用在路由过渡上,形如:

<transition>
  <router-view></router-view>
</transition>

一个简单案例:

//css
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
    transition: all .3s ease;
}
.slide-fade-leave-active {
    transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
    transform: translateX(10px);
    opacity: 0;
}

//vue
<template>
	<div id="app">
           //name设置使用哪个动效。mode设置播放模式,不设置播放模式可能导致“动效错乱”
		<transition name="slide-fade" mode="out-in">
			<router-view />
		</transition>
	</div>
</template>

运行:
在这里插入图片描述
只对某个组件过渡只需要加在该组件上:

// About.vue
<template>
	<transition name="fade" mode="out-in">
		<div class="about">
			<h1>This is an about page</h1>
		</div>
	</transition>
</template>

动态选择动效:

<!-- 使用动态的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>

// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
  '$route' (to, from) {
    const toDepth = to.path.split('/').length
    const fromDepth = from.path.split('/').length
    this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
}

 

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

异步加载

// route/index.js

// 非异步组件
import Home from '../views/Home.vue'
const router = new VueRouter({
  routes: [
    { path: '/home', component: Home }
  ]
})

// 能够被 Webpack 自动代码分割的异步组件
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

 

代码分块

想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

// route/index.js

//以下三个组件都会被打包到 group-foo
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

 

相关资料

Vue Router

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值