手写实现最简版vue-router

472 阅读1分钟

首先vue-router是什么?

官网上的介绍是最准确的,如下图所示。

微信图片_20220131094641.png

知道了是什么,接下来我们将在vue-router里众多的功能里挑出最核心的来手写实现下。

  • [使用hash模式根据路由配置展示对应的组件]

我们还得先知道vue-router在vue项目中是怎么被使用的!

这里我使用vuecli工具新建一个vue2.x项目,并以插件的形式给项目添加vue-router,看看在vue里最基本的使用方式。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// 1.VueRouter是一个插件
Vue.use(VueRouter)

const routes = [{
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/about',
        name: 'About',
        component: () =>
            import ( /* webpackChunkName: "about" */ '../views/About.vue')
    }
]

// 2.创建实例
const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

export default router
import router from './router'

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

1.第一步,引入vue-router,并作为参数传入Vue.use方法执行;这里可以看出 vue-router 是vue的一个插件,所以必须实现一个install的静态方法供Vue.use内部调用。那install方法里实现了什么功能呢?这里先按下不表,在后面的手写实现里,我再将答案抛出。

2.第二步,new VueRouter(options) 创建router实例,并传入一个选项对象,其中包含我们自定义的routes路由配置对象。

3.第三步,在new Vue的时候把router实例作为选项之一传入,这里传入router实例又是为了什么?

开始手写实现

// 保存Vue构造函数,插件中要使用,不导入就能用
let Vue; 

// VueRouter类要求必须有一个install静态方法,将来会被Vue.use调用
class VueRouter {
  constructor(options) {
    // 保存new VueRouter 时传入的选项
    this.$options = options;

    // 使用defineReactive方法让current成为响应式数据,
    // 后续被current依赖的router-view组件根据current变化而渲染对应的组件
    // 将来发生变化,router-view的render函数能够再次执行
    const hash = window.location.hash.slice(1) || "/";
    Vue.util.defineReactive(this, 'current', hash)

    // 监听hash变化
    window.addEventListener("hashchange", () => {
      this.current = window.location.hash.slice(1);
    });
  }
}

// 这将回答上面抛出的第一个问题:那install方法里实现了什么功能呢?
// 1.挂载$router属性
// 2.注册router-view,router-link组件
VueRouter.install = function(_Vue) { // 参数1会在Vue.use调用时传入
  Vue = _Vue;
  
  // 在这里通过mixin的方式延迟到组件的beforeCreate钩子处执行,这时候可访问vue的实例
  Vue.mixin({
    beforeCreate() {
      // 这将回答上面抛出的第二个问题:在new Vue的时候把router实例作为选项之一传入,这里传入router实例又是为了什么?
      // 因为需要把router实例挂载到Vue.prototype上,以后在Vue的任何组件里访问
      if (!Vue.prototype.$router && this.$options.router) {
        Vue.prototype.$router = this.$options.router;
      }
    },
  });

  // 注册 router-link 组件,实际渲染的就是<a>标签
  Vue.component("router-link", {
    props: {
      to: {
        type: String,
        required: true,
      },
    },
    render(h) {
      return h(
        "a",
        {
          attrs: {
            href: "#" + this.to,
          },
        },
        this.$slots.default
      );
    },
  });
  
  // 注册 router-view 组件,根据用户自己配置的routes,与current相匹配找出对应的组件用h方法直接渲染
  Vue.component("router-view", {
    render(h) {
      // 获取当前路由对应的组件
      let component = null
      const route = this.$router.$options.routes.find(
        (route) => route.path === this.$router.current
      );
      if (route) {
        component = route.component
      }
      
      return h(component);
    },
  });
};

export default VueRouter;

以上就是此次最简版的vue-router的手写实现,谢谢看到此处的各位看官老爷。