在使用 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、初始化结束,显示默认页面
正在更新中。。。。