@wy
2020-04-16T13:37:31.000000Z
字数 3459
阅读 491
源码解析
先来回顾下使用 vue-router 的简单过程。
安装:
npm i vue-router --save-dev
使用:
import Vue form 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const router = new VueRouter({} /*路由配置信息*/)
new Vue({
el: '#app',
router: router,
})
三步即可完成路由功能的使用:
沿着这个步骤,来拆解每一步做的事情,并触及到源码分析执行过程。
根据 Vue 的插件机制,给 vue.use() 传对象,由此推断导出对象上,必然会提供一个 install 方法,打开 index.js,简化后代码:
import { install } from './install'
export default class VueRouter {}
VueRouter.install = install
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
导出 VueRouter 类,并挂载 install 方法,提供给 vue.use() 内部调用。
还可以看出,如果在浏览器环境直接引入 vue-router 文件,则不需要手动调用 vue.use(),内部会自动完成。
将目光转向 install.js 文件,完整看这里 https://github.com/vuejs/vue-router/blob/dev/src/install.js。
下面对代码进行拆分,解释每段代码的作用和逻辑。
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {/*代码省略,下面会一段一段分析*/}
引入两个组件,会注册成全局组件,提供在模板中使用。每个组件内部实现,稍后分析。
定义变量 _Vue,存储当前使用的 Vue 构造函数。
导出 install 函数,在 index.js 引入时使用。
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
// 判断是否不为 undefined
const isDef = v => v !== undefined
设置开关,避免重复多次给同一个 Vue 添加插件。
// 此函数,目的是,找到 router-view 组件,把路径对应的组件实例,挂在路由信息的 matched 对象上
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)
}
})
利用 Vue.mixin() 向全局混合功能,意味着每一个组件都要调用混合的函数,提供的 beforeCreate 生命周期钩子函数,会在渲染组件时主动调用。
组件在渲染时,判断此组件上是否有 router 这个值,还记的最开始写的代码:
new Vue({
el: '#app',
router: router,
})
if 条件判断区分是否是根实例:
registerInstance 函数会在 beforeCreate 调用一次,目的是缓存路径对应的组件实例;也会在 destroyed 调用一次,在切换路由卸载组件时,清除缓存的组件实例。
从 if 判断中可以看出来,找到父级中存在 registerRouteInstance 函数,找的就是 router-view 组件。
在定义 router-view 组件时,会在组件数据上添加这个函数。目光暂时转一下,来到 components/view.js 文件中,找到这段代码:
//添加实例
//将在注入的生命周期挂钩中调用
data.registerRouteInstance = (vm, val) => {
const current = matched.instances[name]
if (
(val && current !== vm) ||
(!val && current === vm)
) {
matched.instances[name] = val
}
}
matched 是当前匹配到的路由信息对象,把组件实例缓存到这个对象的 instances 中。
当 val 有值时,添加上;没有值时,清除掉。
回想在组件中调用 this.$router 得到路由对象,this.$route 得到当前访问的路由信息。拿到的是挂载在根组件上的属性,有代码为证:
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
给 Vue 原型上添加属性 $router 和 $route,所有的组件实例都继承 Vue.prototype,自然能够找到这两个属性。
这里处理的很巧妙,当访问时,会触发访问器 get 函数,返回当前组件实例上存的根实例上添加的 _router 和 route 属性。(不明白的可以回看,上面beforeCreated中的逻辑)
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
注册全局组件,将来在组件模板中使用。
const strats = Vue.config.optionMergeStrategies
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
组件选项中可以写路由提供的钩子函数,使用 Vue.mixin() 混合功能时,这些钩子函数的合并规则,和内置的生命周期函数 created 的合并策略保持一致。策略很简单,就是不进行覆盖,而是两个钩子函数先后执行。
来段代码:
// 要混合的功能
let feature = {
beforeRouteEnter(){
console.log('我是混合功能, beforeRouteEnter')
},
created(){
console.log('我是混合功能, created')
}
}
Vue.extend({
mixin:[feature],
beforeRouteEnter(){
console.log('beforeRouteEnter')
},
created(){
console.log('created')
}
})
这段代码给组件混合了两个钩子函数,都会被执行,而不是组件的钩子函数覆盖混合过来的函数。这就是上面设置了合并策略的效果。