vue-router导航守卫进阶&源码实现

3,124 阅读3分钟

前端路由实现起来其实很简单,本质就是监听URL的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。vue-router共有三种守卫,分别是全局守卫、路由独享守卫、组件内的守卫。

导航守卫

  • 全局守卫
    你可以使用 router.beforeEach 注册一个全局前置守卫: ep:设置全局守卫,如果进入about页,没有登陆则重定向到login页
router.beforeEach((to,from,next) => {
      console.log(to.meta)
  // 判断是否登录
  if (to.path === '/about' && !window.isLogin) {
    next('/login?redirect='+to.path);
  } else {
    next();
  }
})
  • 路由独享守卫
{
      path: "/about",
      name: "about",
      meta: {requireLogin:true},  // meta  取值就是to.meta
      beforeEnter(to,from,next) {
        // 判断是否登录  路由独享守卫
        if (!store.state.isLogin) {
          next('/login?redirect='+to.path);
        } else {
          next();
        }
      },
      component: () =>
        import(/* webpackChunkName: "about" */ "./views/About.vue")
}

效果等同于全局守卫

  • 组件内的守卫
export default {
  beforeRouteEnter(to, from, next) {}
  beforeRouteUpdata(to, from, next) {}
  beforeRouteLeave(to, from, next) {}
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

meta路由元信息

ep:通过route设置document.title

  {
    path: '/customer/new',
    name: 'NewCustomer',
    meta: {title: '新增客户'},
    component: () => import('@/Views/CustomerNew')
  },
  
  router.beforeEach(function (to, from, next) {
      if (to.meta && to.meta.title) {
        document.title = ((to.meta && to.meta.title))
      }
      next()
  })

hash模式&&history模式

www.test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面。

History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观

源码实现

应用页面

      <router-link to="#/foo">foo</router-link> |
      <router-link to="#/bar">bar</router-link>

router配置

const Foo = {
  render() {
    return <div>Foo</div>;
  }
};
const Bar = {
  render() {
    return <div>Bar</div>;
  }
};
export default new TRouter(Vue, {
  routes: [
    { 
      path: "/foo", 
      component: Foo
    },
    {
       path: "/bar",
       component: Bar
    }
  ]
});

源码实现

import Vue from "vue";

class TRouter {
  constructor(Vue, options) {
    this.$options = options;
    this.routeMap = {}; // 全局的路由映射  key就是path(唯一的)
    this.app = new Vue({  // 对vue的强依赖
      data: {
        current: "#/"  // 当前路由
      }
    });
    this.init();
    this.createRouteMap(this.$options); 
    this.initComponent(Vue);
  }

  // 初始化 hashchange
  init() {
    // load事件在页面或某个资源加载成功时触发
    window.addEventListener("load", this.onHashChange.bind(this), false);
    // hashchange事件在 URL 的 hash 部分(即#号后面的部分,包括#号)发生变化时触发。
    window.addEventListener("hashchange", this.onHashChange.bind(this), false);
  }

  createRouteMap(options) {
    options.routes.forEach(item => {
      this.routeMap[item.path] = item.component;
    });
  }

  // 注册组件
  initComponent(Vue) {
    Vue.component("router-link", {  // 全局注册
      props: {
        to: String  // 去哪个地址
      },
      render: function (h) { // 传入函数  h 就是createElement
        // <a :href="to"><slot></slot></a>
        // 返回的是虚拟dom
        return h("a", { attrs: { href: this.to } }, this.$slots.default);
      }
    });

    const _this = this;  // 全局this
    Vue.component("router-view", {  // 占位符
      render(h) { // 根据当前路由发生变化  渲染当前组件所匹配的组件
        var component = _this.routeMap[_this.app.current];
        return h(component);
      }
    });
  }

  // 获取当前 hashgetHash() {
    return window.location.hash.slice(1) || "/";
  }

  // 设置当前路径
  onHashChange() {  // 挂载的组件刷新
    this.app.current = this.getHash();  // 获取最新hish赋值
    console.log(this.app.current, 'this.app.current')
  }
}

总结

vue-router对vue也是强依赖关系,简单的也可以理解为是对history的封装,本质上还是实现根据路由不同,返回不同内容。