目录
导航守卫
解析流程
代码演示
router.js
pages/Foo.vue
router/router.js
router/history/base.js
router/util/async.js
router/components/RouterView.vue
接上一篇文章~,代码部分会有部分重叠,想看完整版的代码,看这篇文章的~
导航守卫
当一个路由——检查是否有权限——>另一个路由
vue-router导航守卫有三类:
- 全局守卫:beforeEach、beforeResolve、afterEach
- 路由独享守卫:beforeEnter
- 组件内守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
解析流程
深颜色:组件上的路由守卫
浅颜色:路由表上路由信息的导航守卫
book————>user;跳转的路由过程中的钩子触发顺序:
- beforeRouteLeave(做一些跟销毁相关的处理)
- router.beforeEach(触发全局守卫)
- beforeRouteUpdate(可重用组件【橙色的'/'】是保持不变的,会触发这个钩子)
- beforeEnter(目标路由独享守卫,就是路由表中user的路由守卫)
- beforRouteEnter(user路由对应的组件)
- router.beforeResolve(全局守卫)
- router.afterEach 导航结束触发(它不具备守卫功能,它是个马后炮,这里可以做一些打点,日志处理相关工作,此时跳转路由已经结束)
初始化进入组件时候:守卫顺序是:2、4、5、6、7
代码演示
router.js
// router.js // 路由使用
import Vue from "vue";
// import Router from "vue-router";
import Router from "./router/router";
import Foo from "./pages/Foo";
import Bar from "./pages/Bar";
Vue.use(Router);
const router = new Router({
routes: [
{
path: "/foo",
component: Foo,
beforeEnter(to, from, next) {// 单个路由上的守卫
console.log("/foo::beforeEnter", to, from);
next();
}
},
{ path: "/bar", component: Bar }
]
});
// 全局路由守卫
// to 【要前往的路由】
// from 【来自的路由】
// next 【代表是否可以走入下一个路由】
router.beforeEach((to, from, next) => { // 解析前
console.log("router.beforeEach", to, from);
next()
// next(false); 未通过 全局路由守卫
});
router.beforeResolve((to, from, next) => { // 解析完
console.log("router.beforeResolve", to, from);
next();
});
router.afterEach((to, from) => { // 解析后,没有守卫功能,只是一个后置的钩子函数
console.log("router.afterEach", to, from);
});
export default router;
pages/Foo.vue
// pages/Foo.vue 页面组件
<template>
<div>Foo</div>
</template>
<script>
export default {
// 组件上的路由守卫钩子函数
beforeRouteEnter(to, from, next) {
console.log("foo::beforeRouteEnter", to, from);
next();
},
beforeRouteUpdate(to, from, next) {
console.log("foo::beforeRouteUpdate", to, from);
next();
},
beforeRouteLeave(to, from, next) {
console.log("foo::beforeRouteLeave", to, from);
next();
},
};
</script>
router/router.js
// router/router.js
// 路由表参数构建
import Vue from 'vue'
import RouterView from './components/RouterView'
import RouterLink from './components/RouterLink'
//注册RouterView组件
Vue.component("RouterView", RouterView)
Vue.component("RouterLink", RouterLink)
class RouterTable {
constructor( routes ) {
this._pathMap = new Map()
this.init(routes)
}
// 初始化函数
init(routes) {
const addRoute = (route) => {
this._pathMap.set(route.path,route)
if(route.children) {
// 遍历
route.children.forEach(cRoute => addRoute(cRoute) );
}
}
routes.forEach(route => addRoute(route) );
}
// 找一下path是否存在
match(path) {
let find;
// 获取所有的路径this._pathMap.keys()
for(const key of this._pathMap.keys()) {
if(path === key) {
find = key;
break;
}
}
return this._pathMap.get(find)
}
}
import Html5Mode from './history/html5';
// 抽取出一个公共函数,此函数是全局守卫的函数,全局钩子函数的注册收集
function registerHook(list, fn) {// list:对应钩子函数的列表,钩子函数
list.push(fn);
return () { // 返回的函数,可以帮我们销毁收集的钩子函数
const i = list.indexOf(fn); // 获取fn的索引index
if(i > -1) lists.splice(i, 1); // 删除钩子函数
}
}
// 创建一个构造路由的类
export default class Router {
constructor({ routes = [] }) {// 构造函数
this.routerTable = new RouterTable(routes); // 路由表
this.history = new Html5Mode(this); // 注入了当前Router实例,路由里可以拿Router
// 全局守卫——钩子函数需要收集起来
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
}
init(app) { // vue单页应用
const {history} = this;// 解构出history
history.listen(route => {
//_route是响应式的
app._route = route;
})
// 第一次渲染
history.transitionTo(history.getCurrentLocation());
}
push(to) {
this.history.push();
}
// 全局守卫——钩子,接收了一个可执行函数参数
beforeEach(fn) {
return registerHook(this.beforeHooks, fn)
}
beforeResolve(fn) {
return registerHook(this.resolveHooks, fn)
}
afterEach(fn) {
return registerHook(this.afterHooks, fn)
}
}
Router.install = function() {
Vue.mixin({
beforeCreate() {
if(this.$options.router !== undefined) {// 存在router
this._routerRoot = this;// this指Vue实例
this._router = this.$options.router;
this._router.init(this);
// this._route的路由信息,响应式的,初始值
Vue.util.defineReactive(this, "_route", this._route.history.current);
}
}
})
}
router/history/base.js
// /router/history/base.js
import { runQueue } from "../util/async"
// 监听器【两种Mode都包含的监听器】
export default class BaseHistory {
constructor(router) {// 依赖反转把routeTable注入进来
this.router = router;
this.routeTable = router.routeTable;
}
// 注册监听cb函数
listen(cb) {
this.cb = cb;
}
// 跳转函数
transitionTo(target) {
// 路由匹配
const route = this.routerTable.match(target);
// 如果我们的路由配置上找到了这个地址
// 路由守卫
// 是否跳转
this.confirmTransition(route, () => {
// 通过路由守卫
this.updateRoute(route);
})
}
// 是否可以跳转(路由守卫)
confirmTransition(route, onComplete, onAbort) { // onAbort中断函数
if(route == this.current) {// 如果是当前路由,那就不用跳转了
return;
}
// 守卫队列,顺序执行
const queue = [
...this.router.beforeHooks,// ...数组展开语法
route.beforeEnter,
route.component.beforeRouteEnter.bind(route.instance),// 把这个实例当this上下文传过去
...this.router.resolveHooks,
];
const iterator = (hook, next) => {// hook 守卫
hook(route, this.current, (to)=>{
if(to === false) {
onAbort && onAbort(to);
} else {
next(to);
}
})
}
runQueue(queue, iterator, () => onComplete())
}
// 通过路由守卫后执行此函数
updateRoute(route) {
const from = this.current;
this.current = route;
// 钩子函数cb,触发RouterView模板刷新
this.cb(this.current)
// afterEach // 只是个钩子函数,没有守卫功能
this.router.afterHooks.forEach(hook => {
hook && hook(route, from)
})
}
}
router/util/async.js
// router/util/async.js
// 暴漏出一个执行队列的方法
// queue:队列
// iter:迭代器,对队列中的每一项进行对应操作的回调函数
// end: end函数,在队列执行完毕后执行
export function runQueue(queue, iter, end){
const step = (index) => {
if(index >= queue.length) {
end();
} else {
if(queue[index]){
iter(queue[index], ()=>{
step(index + 1);
})
} else {
step(index + 1);
}
}
}
step(0);
}
router/components/RouterView.vue
// router/components/RouterView.vue
// RouterView组件-渲染页面组件
<script>
export default {
name: 'RouterView',
render() {
const route = this._routerRoot._route;
if(!route) {
return 404;
}
const {component} = route;
const hook = {
init(vnode) {
route.instance = vnode.componentInstance;
console.log('vnode', vnode)
// Vue组件对应的实例
console.log('instance', vnode.componentInstance)
}
}
return <component hook={hook} />;
}
}
</script>