Vue生态及实践 - Vue Router(2)

本文详细介绍了VueRouter的导航守卫机制,包括全局守卫、路由独享守卫和组件内守卫的使用,以及它们在路由跳转过程中的触发顺序。通过代码示例展示了如何在`router.js`、`pages/Foo.vue`等文件中设置和使用这些守卫。同时,还提到了路由解析流程和不同守卫的执行时机,以及`beforeResolve`和`afterEach`这两个非守卫性质的钩子函数。

目录

导航守卫

解析流程

代码演示

router.js

pages/Foo.vue

router/router.js

router/history/base.js

router/util/async.js 

router/components/RouterView.vue


接上一篇文章~,代码部分会有部分重叠,想看完整版的代码,看这篇文章的~

导航守卫

当一个路由——检查是否有权限——>另一个路由

vue-router导航守卫有三类:

  1. 全局守卫:beforeEach、beforeResolve、afterEach
  2. 路由独享守卫:beforeEnter
  3. 组件内守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

解析流程

 

深颜色:组件上的路由守卫

浅颜色:路由表上路由信息的导航守卫

book————>user;跳转的路由过程中的钩子触发顺序:

  1. beforeRouteLeave(做一些跟销毁相关的处理)
  2. router.beforeEach(触发全局守卫)
  3. beforeRouteUpdate(可重用组件【橙色的'/'】是保持不变的,会触发这个钩子)
  4. beforeEnter(目标路由独享守卫,就是路由表中user的路由守卫)
  5. beforRouteEnter(user路由对应的组件)
  6. router.beforeResolve(全局守卫)
  7. 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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值