Vue-Router源码解析

970 阅读2分钟

Vue-Router的使用

  Vue RouterVue.js官方的路由管理器。它和 Vue.js 的核心深度集成。

Vue Router的核心使用步骤

步骤一: 使用vue-router插件 router.js

import Router from 'vue-router';
Vue.use(Router);

  使用一个插件需要执行 Vue.use(Plugin),实际在 Vue 中的 use调用 Plugin 中的 install 方法,并将 Vue 实例当做参数传入 install 方法中

步骤二: 创建Router实例 router.js

// routes为自定义的路由
export default new Router(routes);

  Router是一个构造函数,需要监听Url的变化,并进行响应,替换当前的显示组件

步骤三: 在根组件上添加盖实例: main.js

import router from './router';

new Vue({
  router
}).$mount('#app');

步骤四: 添加路由视图 APP.vue

  Vue-Router需要实现两个全局组件router-linkrouter-view

<router-view></router-view>

步骤五: 导航 xx.vue

<router-link to = "/">Home</router-link>

路由跳转

this.$router.push('/about');

获取路由参数

const { id } = this.$route.params;

Vue Router源码实现及解析

  VueRouter需要实现的功能:

  • 实现一个VueRouter的类并挂载一个 install 方法,以便给Vue.use使用,接收vue实例当做参数
  • 实现两个全局组件 router-linkrouter-view
  • 处理路由选项,监控Url变化并响应
  • 需要将router实例挂载到Vue实例上 实现 Vue.prototype.$router 以及 Vue.prototype.$route

index.js

  1、实现一个VueRouter的类,使用者通过进行new Routerroutes 路由表作为参数传递给VueRouter。并挂载一个 install 方法,给Vue运行 将当前插件挂载到全局。

let Vue;
class VueRouter {
    constructor(options) {
        this.$options.options;
    }
}
// vue插件需要在外部使用Vue.use 进行挂载
// Vue.use会自动执行插件的 install 方法并将Vue实例当做参数传递进来
VueRouter.install = function(_vue) {
  Vue = _vue;
  // 使用mixin,beforeCreate获取顶层vue实例 拿到router
  // 1、挂载$router
  Vue.mixin({
    beforeCreate() {
      // 只有根组件才有router选项, 且在viewRouter组件中也需要用到
      if (this.$options.router) {
        // 方便外部 调用$router -> this.$router
        Vue.prototype.$router = this.$options.router;
      }
    },
  });

  // 2、实现两个组件
  Vue.component("router-link", RouterLink);
  Vue.component("router-view", RouterView);
}

 2、需要监听路由的变化,保存当前命中url对应的路由信息。扩展VueRouter的功能

class VueRouter {
    constructor(options) {
        this.$options.options;
        // 当前路径的地址
        this.current = window.location.hash.slice(1) || "/";
        // 匹配到的组件
        Vue.util.defineReactive(this, 'matched', []);

        window.addEventListener("hashchange", this.onHashChange.bind(this));
        window.addEventListener("load", this.onHashChange.bind(this));

        // 匹配路由
        this.match();
    }
    
    match(routes) {
        if (!routes) {
          routes = this.$options.routes;
        }

        for (let route of routes) {
          // 匹配首页
          if (route.path === "/" && this.current === "/") {
            this.matched.push(route);
            return;
          }

          let pathArr = this.current.split("/");
          // 匹配其他。 /about/foo -> 需要匹配两个 /about 以及 /about/foo
          if (route.path !== "/" && pathArr.includes(route.path.slice(1))) {
            this.matched.push(route);
            if (route.children && route.children.length > 0) {
              this.match(route.children);
            }
          }
        }
      }

      onHashChange() {
        this.current = window.location.hash.slice(1);
        this.matched = [];
        this.match();
      }
}

link.js

  router-link比较简单,本质上其实就是一个简单的 a 标签。接收一个to 属性,跳转都当前的to属性代表的新地址。

export default {
  props: {
    to: {
      require: true,
      type: String,
    },
  },
  render(h) {
    return h(
      "a",
      {
        attrs: {
          href: "#" + this.to,
        },
      },
      [this.$slots.default]
    );
  },
};

view.js

  路由的展示主要是子组件的展示,需要将每一个RouterView进行标记。若当前组件中有多个RouterView嵌套,则可以通过遍历与当前标记结合的方式判断当前RouterView在嵌套组件中的层级,以便获取可匹配的路由信息

export default {
  name: 'RouterView',
  // 在当前组件中 可以通过this.$router获取router实例中的数据: install中已将router实例挂载到了Vue根组件上
  render(h) {
    // 标记当前组件为RouterView组件
    this.$vnode.data.routerView = true;
    // 为了获取当前组件的层级,matched数组中: 比如/about/foo 中存储第一位为父组件/about的路由信息,后面为/abou/foo的子路由信息  层级遍历
    let depth = 0;
    let parent = this.$parent;

    // 判断当前组件的层级
    while (parent) {
      // 获取父级元素的data属性  判断 父组件是否为 routerView组件
      // 若父级组件中存在routerView组件 则当前组件层级需要+1
      const vnodeData = parent.$vnode ? parent.$vnode.data : {};
      if (vnodeData.routerView) {
        depth++;
      }
      parent = parent.$parent;
    }

    // 获取匹配到的组件
    const matched = this.$router.matched;

    let component = null;
    // 获取当前层级匹配的路由信息
    const route = matched[depth];
    if(route) {
      component = route.component;
    }
    return h(component);
  }
}