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

Vue.use

Vue 提供了 Vue.use 的全局 API 来注册这些插件
分析Vue.use的实现原理,定义在 vue/src/core/global-api/use.js 中

  1. export function initUse (Vue: GlobalAPI) {
  2. // 传入plugin参数
  3. Vue.use = function (plugin: Function | Object) {
  4. // _installedPlugins维护的数组,存储所有注册过的plugin
  5. const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
  6. if (installedPlugins.indexOf(plugin) > -1) {
  7. return this
  8. }
  9. // additional parameters
  10. const args = toArray(arguments, 1)
  11. args.unshift(this)
  12. // 判断plugin有没有定义install方法
  13. if (typeof plugin.install === 'function') {
  14. // 调用install方法,并且该方法执行的第一个参数是Vue
  15. plugin.install.apply(plugin, args)
  16. } else if (typeof plugin === 'function') {
  17. plugin.apply(null, args)
  18. }
  19. // 把plugin储存到installedPlugins中
  20. installedPlugins.push(plugin)
  21. return this
  22. }
  23. }

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

路由安装

Vue-Router 的入口文件是 src/index.js,其中定义了 VueRouter 类,也实现了 install 的静态方法:VueRouter.install = install,它的定义在 src/install.js 中

  1. export let _Vue
  2. export function install (Vue) {
  3. // installed标志位
  4. if (install.installed && _Vue === Vue) return
  5. install.installed = true
  6. // 全局_Vue来接收参数Vue
  7. // 因为作为Vue插件对Vue对象是有依赖的,但又不能去单独去import Vue,因为那样会增加包体积
  8. _Vue = Vue
  9. const isDef = v => v !== undefined
  10. const registerInstance = (vm, callVal) => {
  11. let i = vm.$options._parentVnode
  12. if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
  13. i(vm, callVal)
  14. }
  15. }
  16. // 利用Vue.mixin去把beforeCreate和destoryed钩子函数注入到每一个组件中
  17. Vue.mixin({
  18. beforeCreate () {
  19. if (isDef(this.$options.router)) {
  20. // 对于根Vue实例而言
  21. // this._routerRoot表示它自身
  22. this._routerRoot = this
  23. // this._router表示VueRouter的实例router,是在new Vue的时候注入的
  24. this._router = this.$options.router
  25. // 初始化router
  26. this._router.init(this)
  27. // 把this._route变成响应式对象
  28. Vue.util.defineReactive(this, '_route', this._router.history.current)
  29. } else {
  30. this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
  31. }
  32. registerInstance(this, this)
  33. // 对于子组件而言,由于组件是树状结构,在遍历组件树的过程中,它们在执行该钩子函数的时候 this._routerRoot 始终指向的离它最近的传入了 router 对象作为配置而实例化的父实例
  34. },
  35. destroyed () {
  36. registerInstance(this)
  37. }
  38. })
  39. // 给Vue原型上定义了$router和$route这两个属性的get方法
  40. // 这就是为什么可以在组件实例上可以访问 this.$router 以及 this.$route
  41. Object.defineProperty(Vue.prototype, '$router', {
  42. get () { return this._routerRoot._router }
  43. })
  44. Object.defineProperty(Vue.prototype, '$route', {
  45. get () { return this._routerRoot._route }
  46. })
  47. // 定义全局的<router-link>和<router-view>2个组件
  48. Vue.component('RouterView', View)
  49. Vue.component('RouterLink', Link)
  50. const strats = Vue.config.optionMergeStrategies
  51. // use the same hook merging strategy for route hooks
  52. // 定义了路由中的钩子函数和合并策略,和普通钩子函数一样
  53. strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
  54. }

当用户执行Vue.use(VueRouter)时实际上就是在执行install函数
为了确保 install 逻辑只执行一次,用了 install.installed 变量做已安装的标志位
Vue.mixin 定义在 vue/src/core/global-api/mixin.js 中

  1. export function initMixin (Vue: GlobalAPI) {
  2. Vue.mixin = function (mixin: Object) {
  3. // 把要混入的对象通过mergeOptions合并到Vue的options中
  4. this.options = mergeOptions(this.options, mixin)
  5. return this
  6. }
  7. }

由于每个组件的构造函数都会在 extend 阶段合并 Vue.options 到自身的 options 中,所以也就相当于每个组件都定义了 mixin 定义的选项

Vue 编写插件的时候通常要提供静态的 install 方法,我们通过 Vue.use(plugin) 时候,就是在执行 install 方法Vue-Router 的 install 方法会给每一个组件注入 beforeCreate 和 destoryed 钩子函数,在 beforeCreate 做一些私有属性定义和路由初始化工作