前言
为什么读源码?
一、更好地使用api,解决工作中遇到的问题
二、避免一些使用中的常见错误
调用步骤
- 1.安装router插件
import router form vue-router
Vue.use(router)
- 2.实例化router
const Home = { template: 'home' }
let router = new Router({
routes: [{ path: '/home', component: Home }]
});
- 3.挂载router
new Vue({
router,
render(h){
h(App)
}
})
安装router插件
Vue.use是Vue提供的一种插件安装机制,如果插件是一个对象,必须提供install方法。Vue.use会调用插件的install方法。router的install方法主要往vue实例上增加了一些生命周期的处理,并且在vue构造器原型上增加了$router和$route的属性,也就是我们调用的时候使用的this.$router及this.$route。响应式的定义了_route,这样在路由对象改变的时候所依赖的视图文件会跟着更新。
以下是vue-router的install实现
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
//防止重复安装插件
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
//通过全局mixin注入一些生命周期的处理
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this // 定义私有属性_routerRoot,指向Vue实例,用于$router属性和$route属性从中读取值
this._router = this.$options.router //定义私有属性_router,指向VueRouter实例
this._router.init(this) //初始化vue-router
Vue.util.defineReactive(this, '_route', this._router.history.current) // 定义私有属性_route,设置为响应式
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this) // 注册实例
},
destroyed () {
registerInstance(this) // 销毁实例
}
})
// 在Vue的prototype上初始化$router和$route的getter,让其只读,不可更改。分别指向当前Router实例及当前Route信息
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
//全局定义RouterView及RouterLink组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
}
实例化router
实例化vue-router类的过程中主要干了两件事,添加matcher属性和根据mode添加history属性。调用createMatcher方法返回一个Matcher对象,里面包含match和addRoutes两个方法。这两个方法主要是供Vue-router上原型方法match和addRoutes使用。match的主要作用是根据参数location获取获取目标路由对象,这个方法主要在每次路由切换的时候transitionTo方法调用。addRoutes的作用动态添加路由配置,实际开发场景中有可能需要根据不同的人员权限输出不同的界面。添加history属性根据mode参数选择实例化的类,mode共有三种参数,在浏览器环境下有两种方式,分别是在HTML5History,HashHistory两个类中实现的.二者均继承自History类。这里才是整个路由的核心部分,路由切换的核心逻辑处理都包含在这部分内容中。
export default class VueRouter {
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
//创建matcher,一个包含addRoutes和match方法的对象
this.matcher = createMatcher(options.routes || [], this)
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}`)
}
}
}
实例化vue
new Vue({
router,
render(h){
h(App)
}
})
在options中传入router和渲染方法,此时创建一个Vue实例,对应的vueRouter的install方法中混合的beforeCreate钩子就会被调用,根据当前的url(history.getCurrentLocation()),完成第一次的路由导航。这也就是为什么我们开发服务启动以后,hash模式下输入http://localhost:8080,url会变成http://localhost:8080/#/。另外监听history对象,如果变化则会更新vue实例的_route属性。
init (app: any /* Vue component instance */) {
const history = this.history
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
通过代码可以看到VueRouter中实现路由切换使用的是transitionTo方法。这是History类上的一个方法。
总结
本文根据调用vue的步骤大概说了vue-router源码的主要流程,后续会对一些细节的处理及实现进行展开。进行正常调用以后我们使用中的主要逻辑流程如下图