先来简单回复下VueRouter的简单使用

npm install @vue/cli -g
vue create vue-router

Babel 因为代码可能会用到.vue组件,babel会通过vue-loader对.vue组件进行编译

打开项目/src/router/index.js文件

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

可以看到通过Vue.use(VueRouter)来使用VueRouter的。

Vue.use的核心原理

// 伪代码
Vue.use = function(plugin, options) {
  plugin.install(this, options)
}

插件会默认调用install方法,第一个参数this指的Vue的构造函数,第二个参数指的是Vue的一个选项。这样写的原因是为了让项目中的Vue版本和VueRouter中的项目版本一致。

比如vue-router中引入的Vue2.0,项目中Vue的版本是3.0这样就可能出现不兼容的问题。

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

使用new方法调用VueRouter说明VueRouter是一个类。

前端路由实现一般就两种模式 1.hash 2.history

就可以监听hash的变化,来控制组件的显示。

但是这种方案比较丑,但兼容性好

我们实际在页面中访问的路径一般

window.history.pushState({}, null, 'xxx')

但是这样路径并不存在,当我们直接在当前页面刷新就会去服务器请求这个页面,但服务器并没有这个页面,找不到这个页面,所以需要服务端支持。项目里因为用了webpack,webpack中有一个插件history-fallback支持这种跳转。

两种模式都不会像服务器发起真实请求。

#/ 这个是hash #xxx这个是锚点

/src/vue-rouer/index.js

export default class VueRouter {}

function install(Vue, options) {
  console.log(Vue, options)
}

VueRouter.install = install

控制台输出

/src/router/index.js

import Vue from 'vue'
import VueRouter from '@/vue-router'
// ...
Vue.use(VueRouter) // 为了使vue-router中的Vue的版本和用户使用的一致
// ...
export default router

页面输出

这里插入一个小的知识点

export a
settimeinterval(() => {
  a++
}, 1000)
import { a } from './vue'
settimeinterval(() => {
  a++
}, 1000)

a会变吗?

下面我们来分析install中到底做了哪些事?

当我们在如果你要使用到router,你会在实例化Vue的参数options中加入router

src/main.js

new Vue({
  router, // 创建实例时提供router属性,表示初始化路由
  render: h => h(App)
}).$mount('#app')

我们给Vue根实例注入了router属性,所有页面都可以拿到这个属性

install方法的实现

export let _Vue;
export default function install(Vue) {
    _Vue = Vue;
    Vue.mixin({ // 给所有组件的生命周期都增加beforeCreate方法
        beforeCreate() {
            if (this.$options.router) { // 如果有router属性说明是根实例
                // 这里的this指向的是根组件
                this._routerRoot = this; // 将根实例挂载在_routerRoot属性上
                this._router = this.$options.router; // 获取options里面的router配置赋值给根实例的_router属性,也就是VueRouter的实例

                this._router.init(this); // 初始化路由,这里的this指向的是根实例
            } else { // 父组件渲染后会渲染子组件

                // 这里的this指向的当前渲染的组件,当前组件通过$parent找到父组件的_routerRoot属性,由于在上面的if语句中我们已经将根实例赋值给了_routerRoot属性,并将router配置赋值给了根组件的_router属性,所以可以通过$parent._routerRoot获取到根组件,并通过根组件拿到router配置项,再将这个属性赋值给当前组件的_routerRoot属性,当再渲染其他组件的时候就可以一层层获取到router配置了
                this._routerRoot = this.$parent && this.$parent._routerRoot;
                
            }
        }
    })
}

需要将根组件的router赋值子组件,让子组件也能拿到router属性

渲染的时候先渲染根组件再依次渲染其他组件,组件是一层层渲染的

注意install方法中的this指的当前的组件

这里我们应该在Vue-Router上增加一个init方法,主要目的就是初始化功能

这里在强调下,什么是路由? 路由就是匹配到对应路径显示对应的组件!

import createMatcher from './create-matcher'
import install from './install'
export default class VueRouter{
    constructor(options){
        // 根据用户传递的routes创建匹配关系,this.matcher需要提供两个方法 
        // match:match方法用来匹配规则
        // addRoutes:用来动态添加路由
        this.matcher = createMatcher(options.routes || []);
    }
    init(app){}
}
VueRouter.install = install;

编写createMatcher方法

import createRouteMap from './create-route-map'
export default function createMatcher(routes) {
    // 收集所有的路由路径, 收集路径的对应渲染关系
    // pathList = ['/','/about','/about/a','/about/b']
    // pathMap = {'/':'/的记录','/about':'/about记录'...}
    let {pathList,pathMap} = createRouteMap(routes);
    
    // 这个方法就是动态加载路由的方法
    function addRoutes(routes){
        // 将新增的路由追加到pathList和pathMap中
        createRouteMap(routes,pathList,pathMap);
    }   
    function match(){} // 稍后根据路径找到对应的记录
    return {
        addRoutes,
        match
    }
}

这里需要创建映射关系,需要createRouteMap方法

路由的核心原则就是根据路径返回对应的组件