Vue源码 Vue Router(一)路由注册

Vue源码 Vue Router(一)路由注册

学习内容和文章内容来自 黄轶老师
《Vue.js2.0 源码揭秘》
《Vue.js 3.0 核心源码解析》
这里分析的源码是Runtime + Compiler 的 Vue.js
调试代码在:node_modules\vue\dist\vue.esm.js 里添加
vue版本:Vue.js 2.5.17-beta

你越是认真生活,你的生活就会越美好——弗兰克·劳埃德·莱特
《人生果实》经典语录

点击回到 Vue源码学习完整目录

vue-router – GitHub仓库地址

Vue-Router

路由的概念相信大部分同学并不陌生,它的作用就是根据不同的路径映射到不同的视图

我们在用 Vue 开发过实际项目的时候都会用到 Vue-Router 这个官方插件来帮我们解决路由的问题。

Vue-Router 的能力十分强大

  • 它支持 hashhistoryabstract 3 种路由方式,
  • 提供了 <router-link><router-view> 2 种组件,
  • 还提供了简单的路由配置和一系列好用的 API。

大部分同学已经掌握了路由的基本使用,但使用的过程中也难免会遇到一些坑,那么这一章我们就来深挖 Vue-Router 的实现细节,一旦我们掌握了它的实现原理,那么就能在开发中对路由的使用更加游刃有余。

同样我们也会通过一些具体的示例来配合讲解,先来看一个最基本使用例子:

<!-- src/App.vue -->
<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>
// src/main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App'

Vue.use(VueRouter)

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写)相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  el: '#app',
  render(h) {
    return h(App)
  },
  router
})

这是一个非常简单的例子,接下来我们先从 Vue.use(VueRouter) 说起。

路由注册

Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。

Vue-Router 就是官方维护的路由插件,在介绍它的注册实现之前,我们先来分析一下 Vue 通用的插件注册原理

Vue.use

Vue 提供了 Vue.use() 的全局 API 来注册这些插件,所以我们先来分析一下它的实现原理,定义在 vue/src/core/global-api/use.js 中:

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

Vue.use 接受一个 plugin 参数,并且维护了一个 _installedPlugins 数组,它存储所有注册过的 plugin

接着又会判断 plugin 有没有定义 install 方法,如果有的话则调用该方法,并且该方法执行的第一个参数是 Vue;最后把 plugin 存储到 installedPlugins 中。

可以看到 Vue 提供的插件注册机制很简单,每个插件都需要实现一个静态的 install 方法,当我们执行 Vue.use 注册插件的时候,就会执行这个 install 方法,并且在这个 install 方法的第一个参数我们可以拿到 Vue 对象,这样的好处就是作为插件的编写方不需要再额外去import Vue 了。

路由安装

Vue-Router的入口文件是 src/index.js,其中定义了 VueRouter 类,也实现了 install 的静态方法:VueRouter.install = install,它的定义在 src/install.js 中。
vue-router – GitHub仓库地址

export let _Vue
export function install (Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined

  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

当用户执行 Vue.use(VueRouter) 的时候,实际上就是在执行 install 函数,为了确保 install 逻辑只执行一次,用了 install.installed 变量做已安装的标志位。

另外用一个全局的 _Vue 来接收参数 Vue,因为作为 Vue 的插件对 Vue 对象是有依赖的,但又不能去单独去 import Vue,因为那样会增加包体积,所以就通过这种方式拿到 Vue 对象。

Vue-Router 安装最重要的一步就是利用 Vue.mixin 去把 beforeCreatedestroyed 钩子函数注入到每一个组件中。

Vue.mixin 的定义,在 vue/src/core/global-api/mixin.js 中:

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

它的实现实际上非常简单,就是把要混入的对象通过 mergeOptions 合并到 Vueoptions 中,由于每个组件的构造函数都会在 extend 阶段合并 Vue.options 到自身的 options 中,所以也就相当于每个组件都定义了 mixin 定义的选项。

回到 Vue-Routerinstall 方法,先看混入的 beforeCreate 钩子函数,对于根 Vue 实例而言,执行该钩子函数时定义了 this._routerRoot 表示它自身;

this._router 表示 VueRouter 的实例 router,它是在 new Vue 的时候传入的;

另外执行了 this._router.init() 方法初始化 router,这个逻辑之后介绍,然后用 defineReactive 方法把 this._route 变成响应式对象,这个作用我们之后会介绍。

而对于子组件而言,由于组件是树状结构,在遍历组件树的过程中,它们在执行该钩子函数的时候 this._routerRoot 始终指向的离它最近的传入了 router 对象作为配置而实例化的父实例。

对于 beforeCreatedestroyed 钩子函数,它们都会执行 registerInstance 方法,这个方法的作用我们也是之后会介绍。

接着给 Vue 原型上定义了 $router$route 2 个属性的 get 方法,这就是为什么我们可以在组件实例上可以访问 this.$router 以及 this.$route,它们的作用之后介绍。

接着又通过 Vue.component 方法定义了全局的 <router-link><router-view> 2 个组件,这也是为什么我们在写模板的时候可以使用这两个标签,它们的作用也是之后介绍。

最后定义了路由中的钩子函数的合并策略,和普通的钩子函数一样。

总结

那么到此为止,我们分析了 Vue-Router 的安装过程,Vue 编写插件的时候通常要提供静态的 install 方法,我们通过 Vue.use(plugin) 时候,就是在执行 install 方法。

Vue-Routerinstall 方法会给每一个组件注入 beforeCreatedestoryed 钩子函数,在 beforeCreate 做一些私有属性定义和路由初始化工作

下一节我们就来分析一下 VueRouter 对象的实现和它的初始化工作。

Vue源码学习目录

点击回到 Vue源码学习完整目录
vue-router – GitHub仓库地址


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值