手写简版Vue Router

967 阅读1分钟

1、官方Vue Router使用步骤

在使用官方Vue Router时,一般会有以下几个步骤:

  • 引入Vue Router插件:import VueRouter from 'vue-router'
  • 安装路由功能:Vue.use(VueRouter)
  • 定义路由实例:包括路由模式,路由组件,路由规则......
  • 将路由实例挂载到根实例上:new Vue({router}).$mount('#app')

对于这个过程,有两个值得注意的问题:

  • Vue.use【Vue在使用其它插件时也需要执行Vue.use】的作用是什么?
  • 为什么要把Vue Router实例挂载到根实例上?

我们带着这两个问题,来实现自己的简版Vue Route,并从中找到答案。

2、简版Vue Router实现思路

为了实现自己的Vue Router【MyVueRouter】,我们先来理清一下简单的实现思路:

  • 明确MyVueRouter是一个插件,即最后会以Vue.use的方式安装
  • routes选项解析:生成一个map,把路径【hash】和component映射起来
  • 监控url上hash变化,响应hash变化,获取并渲染对应组件
  • 实现两个全局组件:<my-router-link><my-router-view>

3、实现简版Vue Router

// my-vue-router.js
let Vue

class MyVueRouter {
    constructor(opts) {
        this.$opts = opts
        // 创建一个路由path和component的映射
        this.routeMap = {}
        // 将来当前路径current需要响应式,利用Vue响应式原理可以做到这一点
        this.app = new Vue({
            data: {
                current: '/'
            }
        })
    }
    // MyVueRouter的初始化方法
    init() {
        // 绑定浏览器事件
        this.bindEvents()
        // 解析路由配置
        this.createRouteMap(this.$opts)
        // 全局注册my-router-link和my-router-view
        this.initComponent()
    }
    bindEvents() {
        // bind(this)确保this指向
        // 路由初始化
        window.addEventListener('load', this.onHashChange.bind(this))
        // 路由跳转
        window.addEventListener('hashchange', this.onHashChange.bind(this))
    }
    onHashChange() {
        // http://localhost/#/home
        this.app.current = window.location.hash.slice(1) || '/'
    }
    createRouteMap(opts) {
        // routes: [
        //     { path: "/home", component: Home }, 
        //     { path: "/about", component: About },
        // ]
        opts.routes.forEach(item => {
            // ['/home']: {path:'/home',component:Home}
            this.routeMap[item.path] = item.component
        })
        console.log('this.routeMap', this.routeMap)
    }
    initComponent() {
        Vue.component('my-router-link', {
            props: {
                to: {
                    type: String,
                    required: true
                },
            },
            render(h) {
                // <a :href="to">xxx</a>
                // h()写法
                return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
                // JSX写法
                // return <a href={this.to}>{this.$slots.default}</a>
            }
        })
        // hash改变 -> 通过onHashChange监听,从而赋值current -> current通过Vue具有响应性,它的变化会使得[my-router-view]重新执行render
        Vue.component('my-router-view', {
            // 箭头函数能保留this指向,这里指向MyVueRouter实例
            render: h => {
                const Comp = this.routeMap[this.app.current]
                return h(Comp)
            }
        })
    }
}
// 把MyVueRouter变为插件
// Vue.use会执行MyVueRouter的install方法,并把Vue作为参数传给MyVueRouter[对于其他Vue插件也是]
MyVueRouter.install = function (_Vue) {
    Vue = _Vue
    // 混入,扩展Vue
    Vue.mixin({
        beforeCreate() {
            // this指向Vue根组件实例
            // 通过判断是否有“myRouter”选项,确保是根组件时执一次,将MyVueRouter实例放到Vue原型,以后所有组件实例就均有$myRouter
            if (this.$options.myRouter) {
                Vue.prototype.$myRouter = this.$options.myRouter; 
                this.$options.myRouter.init();
            }
        }
    })
}

export default MyVueRouter

4、测试自己的简版Vue Router

// router/test-my-router.js
import Vue from 'vue'
import MyVueRouter from 'my-vue-router'
import Home from '../views/Home'
import About from '../views/About'

// 安装插件
Vue.use(MyVueRouter);

export default new MyVueRouter({
  routes: [
      { path: "/home", component: Home }, 
      { path: "/about", component: About },
  ]
});
// main.js
import Vue from 'vue'
import App from './App.vue'
import myRouter from './router/test-my-router'

Vue.config.productionTip = false

new Vue({
  myRouter,
  render: h => h(App)
}).$mount('#app')
// TestMyRouter.vue
<template>
  <div>
    <my-router-link to="/home">Go to Home</my-router-link>
    <my-router-link to="/about">Go to About</my-router-link>
    <my-router-view />
  </div>
</template>