vue-router 是 Vue.js 官方的路由库,本着学习的目的,我对 vue-router 的源码进行了阅读和分析,分享出来给其余感兴趣的同窗作个参考吧。javascript
文档:vue-router 官方中文教程vue
咱们分别从不一样的视角来看 vue-router。java
从开发者角度来看,是这样的:git
var router = new VueRouter({ routes: [ {path: '/foo', component: {template: '<div>FOO</div>'}}, {path: '/bar', component: {template: '<div>BAR</div>'}} ] }) var vm = new Vue({ el: '#app', router: router })
咱们建立一个 router
,传入的 routes
中的每一项即为一条路由(route)配置,表示在匹配给定的地址时,应该使用什么组件渲染视图。github
将 router
传入 new Vue()
用于建立根组件,这样根组件中对应的视图区域,能够基于 router
中的配置,根据页面地址显示不一样的内容。固然,这还须要在组件模板中使用 <router-view>
来定义区域。vue-router
从视图角度来看,是这样的:app
<div id="app"> ... <router-view></router-view> ... </div>
页面地址变动后,<router-view>
对应的区域会更新为地址匹配的组件。例如,路径是 /foo
则对应区域显示 FOO,路径是 /bar
则显示 BAR,路径没有匹配的组件时,则不显示内容。函数
从数据角度来看,是这样的:工具
vm + _router | $router - history - matcher + _route | $route - matched
vm.$router
引用当前组件对应的 router
对象,该对象在初始化时(在 vm
建立过程当中执行初始化),会启动对页面地址变动的监听,从而在变动时更新 vm
的数据($route
),进而触发视图的更新。
如何实现对地址变动的监听?
对于缺省的 HashHistory
模式(也就是基于页面地址的 hash 部分来实现路由功能,如 http://example.com/path#/foo
、http://example.com/path#/bar
),是经过监听 hashChange
事件来实现:
window.addEventListener('hashchange', () => { // this.transitionTo(...) })
这个动做是何时执行的呢?
是在 router.init()
(源码)中调用的,而 router.init()
则是在根组件建立时(源码)调用的。
而 Vue 组件在建立时,又怎么会去调用 router.init()
呢?
这是因为 vue-router 将自身做为一个插件安装到了 Vue,经过 Vue.mixin()
注册了一个 beforeCreate()
钩子函数,从而在以后全部的 Vue 组件建立时都会调用该钩子函数,给了检查是否有 router
参数,从而进行初始化的机会。进而经过层层调用执行了监听 hashchange
事件的动做。
整理一下:
new Vue()
执行 vue-router 注入的 beforeCreate
钩子函数
执行 router.init(vm)
执行 history.setupListeners()
,注册事件监听
地址变动如何通知到 vm
?
这个过程比较简单,hashchange
时,执行 history.transitionTo(...)
,在这个过程当中,会进行地址匹配,获得一个对应当前地址的 route
,而后将其设置到对应的 vm._route
上。
只是进行了赋值,为何 vm
就能够感知到路由的改变了呢?
答案在 vue-router 注入 Vue 的 beforeCreate
钩子函数中(源码):
Vue.util.defineReactive(this, '_route', this._router.history.current)
采用与 Vue 自己数据相同的“数据劫持”方式,这样对 vm._route
的赋值会被 Vue 拦截到,而且触发 Vue 组件的更新渲染流程。
地址变动若是同步视图更新?
接上一步,vm._route
已经接收到路由的变动,从而触发视图更新。而当视图更新进一步调用到 <router-view>
的 render()
时,即进入了 <router-view>
的处理(源码)。
<router-view>
的 render()
采用函数调用(h()
)模式,而非经过模板生成。这也是 Vue2 支持的定义组件渲染逻辑的方式,相似 React 的 render()
。采用这种模式的好处是能够彻底利用 JavaScript 的能力来编写逻辑,没必要受制于 Vue 的类 HTML 模板语法。
这里的主要处理逻辑是从根组件中取出当前的路由对象(parent.$route
),而后取得该路由下对应的组件,而后交由该组件进行渲染:
return h(component, data, children)
这其中还涉及 <router-view>
嵌套的处理,不过主要逻辑就是这样了。
其实 vue-router 从 <router-view>
的实现来看,就是一个具备特定功能的 Vue 组件而已,不过要配合根组件的 router 发挥做用。但总体仍是很“响应式”的,也是蛮“Vue风格”的。
vue-router 以插件方式“侵入”Vue,从而支持一个额外的 router
属性,以提供监听并改变组件路由数据的能力。这样每次路由发生改变后,能够同步到数据,进而“响应式”地触发组件的更新。
<router-view>
做为根组件下的子组件,从根组件那里能够获取到当前的路由对象,进而根据路由对象的组件配置,取出组件并用其替换当前位置的内容。这样,也就完成整个路由变动到视图变动的过程。
路由变动到视图变动的过程整理为:
hashchange --> match route --> set vm._route --> <router-view> render() --> render matched component
实现过程当中的技术点包括:
Vue 插件机制
响应式数据机制
Vue 渲染机制
地址变动监听
我写了一个应用 Vue.js、vue-router 以及其余 Vue 相关工具(Vuex、vue-loader)的示例,感兴趣能够看下:luobotang/vue-demo - github。