从源码理解 Vue-Router 实现原理

在使用 vue 开发单页面(SPA)应用时,vue-router 是必不可少的技术;它的本质是监听 URL 的变化然后匹配路由规则显示相应的页面,并且不刷新页面;简单的说就是,更新视图但不重新请求页面。

下面通过源码来看一下 vue-router 的整个实现流程:

一、vue-router 源码目录结构

github地址:https://github.com/vuejs/vue-router

当前版本:
vue 2.6.11
vue-router 3.5.1

在这里插入图片描述

components:这里面是两个组件 和
history:这个是路由模式(mode),有三种方式
util:这里是路由的功能函数和类
create-matcher 和 create-router-map 是路由解析和生成配置表
index:VueRouter类,也是整个插件的入口
install:提供插件安装方法

二、Vue.use() 注册插件

vue 在使用路由时需要调用 Vue.use(plugin) 方法进行注册,代码在 vue 源码里面 src/core/global-api/use.js文件里主要作用两个:

1、缓存,避免重复注册
2、使用插件的 install 方法或者直接运行插件来注册

// 初始化use
export function initUse (Vue: GlobalAPI) {
    
    
  Vue.use = function (plugin: Function | Object) {
    
    
    // 检测插件是否已经被安装
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
    
    
      return this
    }
    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    // 调用插件的 install 方法或者直接运行插件,以实现插件的 install
    if (typeof plugin.install === 'function') {
    
    
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
    
    
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}
三、路由安装 install

注册路由的时候需要调用路由的 install 方法,代码在 vue-router 源码的 src/install.js 文件里,是 vue-router 的安装程序,该方法做了下面四件事:

1、缓存,避免重复安装
2、使用 Vue.mixin 混入 beforeCreate 和 destroyed 钩子函数,这样在 Vue 生命周期阶段就会被调用
3、通过 Vue.prototype 定义 router 和 route 属性,方便所有组件使用
4、全局注册 router-view 和 router-link 组件;router-link 用于触发路由的变化,router-view 用于触发对应路由视图的变化

import View from './components/view'
import Link from './components/link'
export let _Vue
// Vue.use安装插件时候需要暴露的install方法 
export function install (Vue) {
    
    
  // 判断是否已安装过,安装过直接 return 出来,没安装执行安装程序 
  if (install.installed && _Vue === Vue) return
  install.installed = true
  // 把Vue赋值给全局变量 
  _Vue = Vue
  // 判断是否已定义 
  const isDef = v => v !== undefined
  //通过registerRouteInstance方法注册router实例 
  const registerInstance = (vm, callVal) => {
    
    
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
    
    
      i(vm, callVal)
    }
  }
  // 混淆进Vue实例,在boforeCreate与destroyed钩子上混淆 
  Vue.mixin({
    
    
    beforeCreate () {
    
    
      // 在option上面存在router则代表是根组件 
      if (isDef(this.$options.router)) {
    
    
        // 根路由设置为自己
        this._routerRoot = this
        // 保存router
        this._router = this.$options.router
        // VueRouter对象的init方法 
        this._router.init(this)
        // Vue内部方法,为对象defineProperty上在变化时通知的属性,实现响应式
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
    
    
        // 非根组件则直接从父组件中获取,用于 router-view 层级判断
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      // 通过registerRouteInstance方法注册router实例
      registerInstance(this, this)
    },
    destroyed () {
    
    
      registerInstance(this)
    }
  })
  //在Vue.prototype挂载属性,可以通过 this.$router、this.$route 来访问 Vue.prototype 上的 _router、_route
  Object.defineProperty(Vue.prototype, '$router', {
    
    
    get () {
    
     return this._routerRoot._router }
  })
  Object.defineProperty(Vue.prototype, '$route', {
    
    
    get () {
    
     return this._routerRoot._route }
  })
  // 注册router-view以及router-link组件 
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  // 该对象保存了两个option合并的规则 
  const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
四、VueRouter 实例化

安装路由插件之后,会对 VueRouter 进行实例化然后将其传入 Vue 实例的 options 中,在 vue-router 源码的 src/index.js 文件里;下面是 VueRouter 的构造函数。

  constructor (options: RouterOptions = {
    
    }) {
    
    
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    // 路由匹配对象
    this.matcher = createMatcher(options.routes || [], this)
	// 根据 mode 采取不同的路由方式
    let mode = options.mode || 'hash'
    this.fallback =
      mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
    
    
      mode = 'hash'
    }
    if (!inBrowser) {
    
    
      mode = 'abstract'
    }
    this.mode = mode

    switch (mode) {
    
    
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
    
    
          assert(false, `invalid mode: ${
    
    mode}`)
        }
    }
  }

构造函数有两个重要的东西:
1、创建路由匹配对象 matcher(核心)

2、根据 mode 采取不同的路由方式

1、vue-router 一共有三种路由模式(mode):hash、history、abstract,其中 abstract 是在非浏览器环境下使用的路由模式,如 weex
2、默认是 hash 模式,如果传入的是 history 模式,但是当前环境不支持则会降级为 hash 模式
3、如果当前环境是非浏览器环境,则强制使用 abstract 模式
4、模式匹配成功则会进行对应的初始化操作

五、路由解析
六、路由切换
七、路由同步
八、scrollBrhavior
、路由组件

1、router-view:组件挂载
2、router-link:路由跳转

最后、路由实现的过程

1、Vue.use(Router) 注册
2、注册时调用 install 方法混入生命周期,定义 router 和 route 属性,注册 router-view 和 router-link 组件
3、生成 router 实例,根据配置数组(传入的routes)生成路由配置记录表,根据不同模式生成监控路由变化的History对象
4、生成 vue 实例,将 router 实例挂载到 vue 实例上面,挂载的时候 router 会执行最开始混入的生命周期函数
5、初始化结束,显示默认页面

正在更新中。。。。

猜你喜欢

转载自blog.csdn.net/weixin_43299180/article/details/114932543