【VueRouter 源码学习】第三篇 - 路由插件 install 的实现

304 阅读4分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战


一,前言

上篇,介绍了路由的配置和使用,主要包含以下内容:

  • 路由插件的安装、配置和使用;
  • 介绍了VueRouter主要功能,标签和属性;
  • 介绍了嵌套路由的使用和注意事项;

本篇,介绍路由插件 install 的实现;


二,路由插件 install 的实现

上一篇,介绍 VueRouter 插件的使用中提到:

执行Vue.use(Router)之后,会默认调用 Router 插件的 install 方法:

  • 在 Vue 全局注册两个组件:<router-link><router-view>
  • 为 vue 实例提供两个原型上的属性:$router$route

本篇,就来自己创建一个 vue-router 插件并实现 install 插件安装逻辑;

1,创建 vue-router 目录结构

为了测试手写的 vue-router 插件,在项目中创建一个 vue-router 插件目录,并在项目中使用;

在 vue-router 的目录中,应该包含以下几个部分:

  • components 目录:

    包含 router-link 和 router-view 两个组件;

  • history 目录:

    页面历史管理,包含 Hash 和 History 两种路由模式;

  • index.js 插件入口文件:

    当引入 vue-router 时,将默认执行插件入口文件 index.js 中的逻辑;

  • install.js 插件安装文件:

    当执行 Vue.use 加载路由插件时,将执行插件安装文件 install.js 中的逻辑;

vue-router 源码的目录结构,如下图所示:

image.png

(提取公用部分 到base.js)


2,插件引入入口 index.js 逻辑实现

上一篇,介绍了路由的使用方式如下:

// router.js

import Vue from 'vue';
import Router from './vue-router';

Vue.use(Router);

export default new Router({
  // 路由配置...
});

从 vue-router 插件的使用方式可以看出:

  • vue-router 插件最终的导出结果是一个类 Router;
  • Router 实例化时,可以传入一个路由配置对象;
  • Router 类能够被 Vue.use(),说明 Router 类上包含了 install 方法;

当 vue-router 插件被引入时,会默认执行 vue-router 目录下的 index.js 入口文件;

所以,在 index.js 中创建一个 VueRouter 类,具有 install 方法,并将其导出:

// vue-router/index.js

// 导如路由安装逻辑
import install from './install'

class VueRouter {
    constructor(options) {  // 传入路由配置对象
      
    }
}

// 当 Vue.use 时,会自动执行插件上的 install 方法;
VueRouter.install = install; 

// 插件最终导出 VueRouter 类,外部实例化时可传入配置对象
export default VueRouter;

3,插件安装入口 install.js 逻辑实现

install.js 最终会导出一个符合 Vue 插件机制的函数:

// vue-router/install.js

// 用于存储插件安装时传入的 Vue 并向外抛出,提供给插件中的其他文件使用
// export 的特点:如果导出的值发生变化,外部会取得变化后的新值;
export let _Vue;   

/**
 * 插件安装入口 install 逻辑 
 * @param {*} Vue     Vue 的构造函数
 * @param {*} options 插件的选项
 */
export default function install(Vue,options){

  _Vue = Vue; // 存储插件安装时使用的 Vue
  
  // 在 Vue 全局上注册两个组件:`<router-link>` 和 `<router-view>`;
  Vue.component('router-link', {
    render: h => h('a', {}, '')
  });
  Vue.component('router-view', {
    render: h => h('div', {}, '')
  });
  
  // 在 Vue 原型上添加两个属性:`$router` 和 `$route`;
  Vue.prototype.$route = {};
  Vue.prototype.$router = {};
}

在 install.js 中,主要包含以下逻辑:

  • 插件安装时,指定插件依赖的 Vue 版本,并导出提供 vue-router 插件其他逻辑使用;
  • 在 Vue 全局上注册两个组件:<router-link><router-view>
  • 在 Vue 原型上添加两个属性:$router$route

4,为所有组件混入 router 实例

当 new Vue 进行初始化时,会将到配置完成的 router 实例注册到 Vue 实例中,相当于在根组件中注册了 router 路由实例:

// main.js

import Vue from 'vue';
import router from './router';  // 导入配置完成的路由实例
import App from './App.vue';

const vm = new Vue({
  el:'#app',
  router, // 将路由实例注册到 Vue 实例中
  render:(h)=>{
    return h(App);
  }
});

为了使每个组件的实例都能够拿到根组件中的router路由实例,即:将路由实例对所有组件共享;

实现方案: 通过 Vue.mixin 为每个组件混入根组件上的 router 实例:

在每个组件创建前,为其混入beforeCreate生命周期函数,并绑定 router 实例,实现 router 实例在所有组件上的共享:

// vue-router/install.js

export let _Vue;

/**
 * 插件安装入口 install 逻辑
 * @param {*} Vue     Vue 的构造函数
 * @param {*} options 插件的选项
 */
export default function install(Vue, options) {

  _Vue = Vue;

  // 通过生命周期,为所有组件混入 router 属性
  Vue.mixin({
      beforeCreate() { // this 指向当前组件实例
      // 将 new Vue 时传入的 router 实例共享给所有子组件
      if (this.$options.router) {// 根组件才有 router
        this._routerRoot = this; // 为根组件添加 _routerRoot 属性指向根组件自己
        this._router = this.$options.router;// this._router 指向 this.$options.router
      } else { // 子组件
        // 如果是子组件,就去找父亲上的_routerRoot属性,并继续传递给儿子
        this._routerRoot = this.$parent && this.$parent._routerRoot;
      }
      // 这样,所有组件都能够通过 this._routerRoot._router 获取到同一个 router 实例;
    }
  });

  // 注册全局组件:router-link、router-view
  Vue.component('router-link', {
    render: h => h('a', {}, '')
  });
  Vue.component('router-view', {
    render: h => h('div', {}, '')
  });
  
  // 注册原型方法 $route、$router
  Vue.prototype.$route = {};
  Vue.prototype.$router = {};
}

这样,所有组件都能够通过this._routerRoot._router获取到同一个 router 实例;

解析:
this._routerRoot = this; 
this._router = this.$options.router;

可以通过 this._routerRoot._router 拿到 this.$options.router;
为子组件添加 router 实例时,通过 this.$parent._routerRoot 就可以获取到父组件上的 router 实例了;
这样,在每一个组件渲染前,通过 mixin 混入 beforeCreate 生命周期中,将最初根组件传入的 router 实例,一层一层地共享并传递下去;

测试取 App.vue 中的 router 实例:

<template>
    <div>
        <!-- 添加路由切换 -->
        <router-link to="/">跳转至首页</router-link> 
        &nbsp
        <router-link to="/mine">跳转至我的</router-link>
        <!-- 页面渲染结果 -->
        <router-view></router-view>
    </div>
</template>


<script>
export default {
    name:'app', 
    mounted(){
        console.log(this._routerRoot._router)
    }
}
</script>

在组件挂在完成后的 mounted 生命周期中输出:

image.png


三,结尾

本篇,介绍路由插件 install 的实现,主要包含以下内容:

  • 创建 vue-router 目录结构;
  • 插件引入入口 index.js 逻辑实现;
  • 插件安装入口 install.js 逻辑实现;
  • 为所有组件混入 router 实例;

下篇,创建路由映射;


维护日志

  • 20210820:
    • 调整文章布局、使用准确的标点符号;
    • 重新组织了部分内容,使表述更易于理解;
    • 新增 2 处重要逻辑的解析说明;
    • 调整“结尾”部分和文章摘要;