实现简单版VueRouter | 8月更文挑战

160 阅读2分钟

介绍

本文目的是实现一个简易版本的VueRouter用以学习,也是对从网课学习的总结和复习。其中内容仅为hash模式下的简易实现,且未处理嵌套路由和路由守卫等情况,多有不足之处,请多多交流。

内容拆分

要实现的简单版VueRouter主要分为两大部分:

  • 主体Router类

    • 保存构造函数的传入的参数
    • 获取当前路由的路径并设置监听事件
  • install方法

    • 保存参数中Vue的构造方法
    • 采用mixin混入方式挂载$router
    • 注册全局组件:router-view、router-link

插件测试

先修改插件的引入信息以供实现过程中输出信息查看,也便于发现错误问题。

  • 复制router文件夹并修改main.js中引入路径
// 修改前
import router from "./router";
// 修改后:复制后的文件夹重命名为krouter
import router from "./krouter";

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");
  • 创建kvue-router.js并修改index.js中引入路径
// 修改前
import VueRouter from "vue-router";
// 修改后
import VueRouter from "./kvue-router";

到此,就修改好了,可以开始插件的实现了。

插件实现

架构搭建

首先搭建一个插件的架子,根据上文的内容拆分

  • 主体Router类
  • install方法

可以得到如下代码:

class Router{}
Router.install = ()=>{}
export default Router

install方法实现

在install方法中,主要任务是

  • 保存参数中Vue的构造方法
  • 采用mixin混入方式挂载$router
  • 注册全局组件:router-view、router-link

保存Vue构造方法

在install方法中,传入的参数为Vue的构造方法。

具体原因是在router/index.js中所执行的 Vue.us(VueRouter) 中调用了install方法,使得Vue构造方法可以以参数形式传入install中。

这也是Vue插件的一个固定写法,install中通过保存参数为全局变量的做法可以在不引入Vue的情况下使用Vue的方法,既减少了对Vue的依赖也缩小了插件体积。

let Vue;
Router.install = (_Vue)=>{
    Vue = _Vue;
}

挂载$router

这里采用混入方式的原因是Vue.use(VueRouter)执行的时候通过 new Vue({}) 创建的Vue实例还没有执行,但是挂载时候需要从Vue实例中获取到router数据,所以采用混入的方式可以将挂载的时机延后至Vue实例创建完毕。

Vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router;
      }
    },
  })

创建全局组件

VueRouter中的全局组件有两个:router-link和router-view。

在实现组件之前,我们需要知道一个事情:通过Vue-Cli生成的项目环境是基于Webpack的运行时版本,不包含编译器。如果在创建组件时我们通过template模板方式,则只能够在运行时环境使用,无法执行编译,所以只能够通过render渲染函数的方式来实现。

router-link

router-link标签有一个to属性,是必须有的。而且router-link标签内部可以有其他内容,所以需要通过插槽获取内部内容。

// krouter-link.js
export default {
  props: {
    to: {
      type: String,
      required: true
    }
  },
  render(h) {
    return h('a', { attrs: { href: "#" + this.to } }, this.$slots.default)
  },
}
//kvue-router.js
import Link from './krouter-link.js';
Vue.component('router-link', Link);

router-view

router-view组件的实现需要Router类中数据的配合,所以留后待述。

Router类实现

在Router类中,主要任务是:

  • 保存构造函数的传入的参数
  • 获取当前路由的路径并设置监听事件

保存构造函数参数

在Router类的构造函数中,参数为router/index.js中的routes等信息。

class Router {
  constructor(options) {
    this.$options = options;
  }
}

获取当前路由并设置监听事件

本次仅实现hash模式,history模式留待后续。

从地址栏获取到当前路由的path路径并将其保存为响应式数据。对 hashchange 和 load 事件设置监听。

class Router {
  constructor(options) {
    this.$options = options;

    // 获取当前路由路径
    const current = window.location.hash.slice(1) || '/';
    // 该方法是Vue实例的工具方法,作用是给this添加一个响应式属性current,并附上初值;
    // current只有是响应式数据,才能在值变化时促使render函数重新执行,组件重新渲染页面
    Vue.util.defineReactive(this, 'current', current);

    window.addEventListener('hashchange', this.onHashChange.bind(this));
    window.addEventListener('load', this.onHashChange.bind(this));
  }
  onHashChange() {
    this.current = window.location.hash.slice(1);
  }
}

至此,主体Router类实现完毕,可以开始补充上文未实现的router-view组件

补充router-view组件实现

router-view组件中需要根据current信息在routes中遍历查找相匹配的路由信息,并从中获取组件配置对象。

export default {
  render(h) {
    let component = null;
    let route = this.$router.$options.routes.find(route => route.path === this.$router.current);
    if (route) {
      component = route.component;
    }
    return h(component)
  },
}

至此,可以在页面中点击查看测试效果。