# 注册插件

上一小节,我们分析了VueRouter的初始化过程,直到初始化第一步就是注册插件。注册插件的调用代码很简单

Vue.use(VueRouter);
成功
1

我们之前分析Vue2 源码的时候,说过插件注册的方式是通过Vue.use调用插件内的install方法。也就是说,上述代码实际上就是调用了VueRouter.install方法,我们只要找到该方法,就可以找到它是在注册的时候做了哪些事情。

还记得上一节,我们找到源码最终入口的时候的代码么

import { install } from './install'

export default class VueRouter {
  // ...
}

VueRouter.install = install
成功
1
2
3
4
5
6
7

在这里,install方法被挂在到了VueRouter上,很明显,这就是我们想找的方法,找到install的定义

export let _Vue

export function install (Vue) {
  // 防止重复注册
  if (install.installed && _Vue === Vue) return
  install.installed = true
  // 缓存Vue实例
  _Vue = Vue

  // 判断传入参数v是否定义过
  const isDef = v => v !== undefined

  /**
   * 将子组件实例注册到父组件实例
   * @param {*} vm  Vue 组件实例,即要注册的子组件实例
   * @param {*} callVal 可选参数,用于在注册时传递额外的数据
   */
  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (
      // 父组件的 VNode 是否存在
      isDef(i) && 
      // 父组件 VNode 的 data 属性是否存在
      isDef(i = i.data) && 
      // data 属性中是否定义了 registerRouteInstance 方法
      isDef(i = i.registerRouteInstance)
    ) {
      // 调用registerRouteInstance注册
      i(vm, callVal)
    }
  }

  // 将路由相关的逻辑混入到每个 Vue 组件实例中
  // 在 beforeCreate 钩子执行时,会初始化路由
  // 在 destroyed 钩子执行时,会卸载路由
  Vue.mixin({
    beforeCreate () {
      // 判断组件是否存在 router 对象,该对象只在根组件上有
      if (isDef(this.$options.router)) {
        // 设置根组件
        this._routerRoot = this
        // 设置vue router实例
        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)
    }
  })

  // 设置代理,当访问 this.$router 的时候,代理到 this._routerRoot._router
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  // 设置代理,当访问 this.$route 的时候,代理到 this._routerRoot._route
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  // 全局注册组件 router-link 和 router-view
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
  
  // router 的钩子函数都使用与 vue.created 一样的mixin 合并策略。
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

代码不多,我们一起分析下上述代码做了哪些事情

  1. 防止重复注册组件实例,通过增加内部标识installed 和缓存Vue实例_Vue,在下一次调用时判断为已注册过,不重复注册
  2. 将路由相关的逻辑通过Vue.mixin混入到每个 Vue 组件实例的beforeCreatedestroyed钩子函数中
  3. 设置$router$route的代理,分别指向当前实例上的_router以及_route属性
  4. 全局注册组件router-linkrouter-view
  5. 设置路由的钩子函数与 vue.created 一样的 mixin 合并策略

这里需要注意的是,虽然我们在new Vue之前调用的Vue.use,但实际执行Vue.use的时机是在new Vue之后的

这里我们只需要记住组件注册的时候发生了什么事情就行,具体逻辑我们在后续分析,等不及的同学可以先看上述注释