前言

本文是vue-router 2.x源码分析的第一篇,主要看vue-router的整体结构!

实例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <h1>Basic</h1>
  10. <ul>
  11. <li><router-link to="/">/</router-link></li>
  12. <li><router-link to="/foo">/foo</router-link></li>
  13. <li><router-link to="/bar">/bar</router-link></li>
  14. <router-link tag="li" to="/bar" :event="['mousedown', 'touchstart']">
  15. <a>/bar</a>
  16. </router-link>
  17. </ul>
  18. <router-view class="view"></router-view>
  19. </div>
  20. <script src='vue.js'></script>
  21. <script src='vue-router.js'></script>
  22. <script>
  23. const Home = { template: '<div>home</div>' }
  24. const Foo = { template: '<div>foo</div>' }
  25. const Bar = { template: '<div>bar</div>' }
  26. //创建router实例
  27. const router = new VueRouter({
  28. routes: [
  29. { path: '/', component: Home },
  30. { path: '/foo', component: Foo },
  31. { path: '/bar', component: Bar }
  32. ]
  33. })
  34. //创建vue实例
  35. new Vue({
  36. router
  37. }).$mount('#app')
  38. </script>
  39. </body>
  40. </html>

1、执行Vue.use(VueRouter)

VueRouter是作为Vue的插件存在的,使用Vue.use(VueRouter)的方式侵入Vue,这个操作在引入vue-router.js时就执行了,Vue.use方法会执行VueRouter的install方法,看下该方法:

  1. function install (Vue) {
  2. if (install.installed) { return }
  3. install.installed = true;
  4. _Vue = Vue;
  5. //1、将$router和$route定义为Vue.prototype的存取器属性,以便所有组
  6. //件都可以访问到,注意这个get函数,是返回this.$root._router而不是
  7. //this._router,这是因为在beforeCreate中将_router放在了vue根实例上
  8. Object.defineProperty(Vue.prototype, '$router', {
  9. get: function get () { return this.$root._router }
  10. });
  11. Object.defineProperty(Vue.prototype, '$route', {
  12. get: function get () { return this.$root._route }
  13. });
  14. var isDef = function (v) { return v !== undefined; };
  15. var registerInstance = function (vm, callVal) {
  16. var i = vm.$options._parentVnode;
  17. if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
  18. i(vm, callVal);
  19. }
  20. };
  21. //2、使用mixin构造了一个beforeCreate函数,该函数会在vue实例创建的
  22. // 时候被调用
  23. Vue.mixin({
  24. beforeCreate: function beforeCreate () {
  25. if (isDef(this.$options.router)) {
  26. this._router = this.$options.router;
  27. //执行router的初始化
  28. this._router.init(this);
  29. //route定义为响应式以便能在其改变时触发vue的更新机制
  30. Vue.util.defineReactive(this, '_route', this._router.history.current);
  31. }
  32. registerInstance(this, this);
  33. },
  34. destroyed: function destroyed () {
  35. registerInstance(this);
  36. }
  37. });
  38. // 3、增加vue的默认组件
  39. Vue.component('router-view', View);
  40. Vue.component('router-link', Link);
  41. var strats = Vue.config.optionMergeStrategies;
  42. // use the same hook merging strategy for route hooks
  43. strats.beforeRouteEnter = strats.beforeRouteLeave = strats.created;
  44. }

以上三件事执行完,就开始new VueRouter了

2、创建VueRouter实例(new VueRouter(options))

  1. function VueRouter (options) {
  2. if ( options === void 0 ) options = {};
  3. this.app = null;
  4. this.apps = [];
  5. this.options = options;
  6. this.beforeHooks = [];
  7. this.resolveHooks = [];
  8. this.afterHooks = [];
  9. //1、根据传入的options.routes创建matcher对象
  10. this.matcher = createMatcher(options.routes || [], this);
  11. //2、根据传入的options.mode创建不同的history对象
  12. var mode = options.mode || 'hash';
  13. this.fallback = mode === 'history' && !supportsPushState;
  14. if (this.fallback) {
  15. mode = 'hash';
  16. }
  17. if (!inBrowser) {
  18. mode = 'abstract';
  19. }
  20. this.mode = mode;
  21. switch (mode) {
  22. case 'history':
  23. this.history = new HTML5History(this, options.base);
  24. break
  25. case 'hash':
  26. this.history = new HashHistory(this, options.base, this.fallback);
  27. break
  28. case 'abstract':
  29. this.history = new AbstractHistory(this, options.base);
  30. break
  31. default:
  32. {
  33. assert(false, ("invalid mode: " + mode));
  34. }
  35. }
  36. };
  • 先看第一件事,由routes创建matcher
  1. //routes长这样:
  2. routes: [
  3. { path: '/', component: Home },
  4. { path: '/foo', component: Foo },
  5. { path: '/bar', component: Bar }
  6. ]
  7. //经过createMatcher处理的matcher长这样:
  8. matcher:{
  9. addRoutes:function addRoutes(routes)
  10. match:function match( raw, currentRoute, redirectedFrom )
  11. __proto__:Object
  12. }
  • 再看第二件事,由mode创建history
  1. //mode默认值为'hash',故会创建HashHistory实例
  2. history:{
  3. base:""
  4. current:Object
  5. errorCbs:Array(0)
  6. pending:null
  7. ready:false
  8. readyCbs:Array(0)
  9. readyErrorCbs:Array(0)
  10. router:VueRouter
  11. __proto__:History
  12. }

最后创建的router实例长这样:

  1. router:{
  2. afterHooks:Array(0)
  3. app:null
  4. apps:Array(0)
  5. beforeHooks:Array(0)
  6. fallback:false
  7. history:HashHistory
  8. matcher:Object
  9. mode:"hash"
  10. options:Object
  11. resolveHooks:Array(0)
  12. currentRoute:(...)
  13. __proto__:Object
  14. }

创建的具体细节以后会分析。这两件事情做完就开始new Vue(options)了。

3、创建Vue实例(new Vue(options))

创建Vue实例的过程中会调用beforeCreate函数,故在第一节中定义的beforeCreate会得到执行:

  1. function beforeCreate () {
  2. if (isDef(this.$options.router)) {
  3. //1、正式将第二节创建的router实例挂在this._router上,这个this
  4. //是根实例,相当于子组件中的this.$root,因此在子组件中能通过t
  5. //his.$router访问到
  6. this._router = this.$options.router;
  7. //2、执行router的初始化
  8. this._router.init(this);
  9. //3、将_route定义为响应式以便能在其改变时触发vue的更新机制
  10. Vue.util.defineReactive(this, '_route', this._router.history.current);
  11. }
  12. //4、注册组件实例
  13. registerInstance(this, this);
  14. },

这里主要看下第二步router的初始化:

  1. function init (app /* Vue component instance */) {
  2. //注意this是VueRouter实例,app是Vue实例
  3. var this$1 = this;
  4. //将当前Vue实例app存入VueRouter实例this.apps数组中,因为一
  5. //个应用可能不止一个Vue实例,故用数组保存。
  6. this.apps.push(app);
  7. // main app already initialized.
  8. if (this.app) {
  9. return
  10. }
  11. //将当前Vue实例app存在VueRouter实例this.app下
  12. this.app = app;
  13. var history = this.history;
  14. //当点击一个路径时,页面会绘制该路径对应的组件,history
  15. //.transitionTo方法就是干这个事的,后续再分析
  16. if (history instanceof HTML5History) {
  17. history.transitionTo(history.getCurrentLocation());
  18. } else if (history instanceof HashHistory) {
  19. var setupHashListener = function () {
  20. history.setupListeners();
  21. };
  22. history.transitionTo(
  23. history.getCurrentLocation(),
  24. setupHashListener,
  25. setupHashListener
  26. );
  27. }
  28. //监听route,一旦route发生改变就赋值给app._route从而触发页面
  29. //更新,达到特定route绘制特定组件的目的
  30. history.listen(function (route) {
  31. this$1.apps.forEach(function (app) {
  32. app._route = route;
  33. });
  34. });
  35. };

beforeCreate函数执行完后,继续Vue实例化的过程,这里就回到了Vue渲染页面的过程,如下图:

VueRouter源码分析(1)--主要执行步骤 - 图1

4、小结

以上分析可以看出引入vue-router时代码的执行流程:

  1. 执行install方法。主要做了3件事:a、将VueRouter源码分析(1)--主要执行步骤 - 图2route定义为Vue.prototype的存取器属性;b、使用Vue.mixin方法创建了beforeCreate函数;c、扩充了Vue的默认组件,即增加了router-link和router-view两个组件。
  2. 创建VueRouter实例。主要做了2件事:a、根据routes创建matcher;b、根据mode创建history。
  3. 创建Vue实例。主要做了1件事:调用beforeCreate函数,继而执行router.init方法去完善router对象。