# vue-router原理
# 前言
vue
作为最受欢迎的前端框架之一,vue-route
r又是官方推出的路由管理库SPA
思想- 面试
- 源码预览 (opens new window)
- 本文基于
vue-router@3.x
版本
# 应用配置
一般会这么配置
shell安装
npm install (or yarn add) vue-router
router.js
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: ....,
base,
mode: 'hash | history',
...others
})
export default router;
main.js
import router from 'router.js';
new Vue({
router,
...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
页面使用路由跳转
# 思考几个点
上面这几段代码值得思考的有几个点
- router.js中单独引入vue,并通过vue.use()挂载到全局,和main.js中引入的vue,会是同一个实例吗?
- vue.use()这中间会涉及到什么
- vue-router暴露了router-link、router-view这两个全局组件
- 另外vue原型挂载了$route、$router这个实例、并附带了一些方法。如push、replace等等
- 另外vue-router有三种mode模式。history、hash、abstract这中间到底干了什么
下面带着这些点逐步探究
# 不同文件间引入vue、这中间有影响吗?(main.js中引入vue、和router.js中都从vue包中引入,最后实例this会有冲突吗?)
不会
可以这么理解,单文件作用域都是隔离的,也就是说这中间vue,只会引入一次,符合tree-shaking,最后都会合并同一个实例上面
# vue.use()执行机制
vue.use注册一个组件,会调用这个组件实例上的install方法、并将vue以形参传入install方法
# 首先挂载vue-router
import { install } from './install';
import { createMatcher } from './create-matcher';
import { HashHistory } from './history/hash';
import { HTML5History } from './history/html5';
import { AbstractHistory } from './history/abstract';
const inBrowser = typeof window !== 'undefined';
export default class VueRouter {
constructor(options) {
// 路由配置
this.options = options;
// 创建路由matcher对象,传入routes路由配置列表及VueRouter实例,主要负责url匹配
this.matcher = createMatcher(options.routes);
let mode = options.mode || 'hash';
// 支持所有 JavaScript 运行环境,非浏览器环境强制使用abstract模式,主要用于SSR
if (!inBrowser) {
mode = 'abstract';
}
this.mode = mode;
// 根据不同mode,实例化不同history实例
switch (mode) {
case 'history':
console.log('history');
this.history = new HTML5History(this);
break;
case 'hash':
console.log('hash');
this.history = new HashHistory(this);
break;
case 'abstract':
console.log('abstract');
this.history = new AbstractHistory(this);
break;
default:
if (process.env.NODE_ENV !== 'production') {
throw new Error(`[vue-router] invalid mode: ${mode}`);
}
}
}
init(app) {
// 绑定destroyed hook,避免内存泄露
app.$once('hook:destroyed', () => {
this.app = null;
if (!this.app) this.history.teardown();
});
// 存在即不需要重复监听路由
if (this.app) return;
this.app = app;
// 跳转当前路由path匹配渲染 用于页面初始化
this.history.transitionTo(
// 获取当前页面 path
this.history.getCurrentLocation(),
() => {
// 启动监听
this.history.setupListeners();
}
);
// 传入赋值回调,为_route赋值,进而触发router-view的重新渲染,当前路由对象改变时调用
this.history.listen((route) => {
app._route = route;
});
}
// 匹配路由
match(location) {
return this.matcher.match(location);
}
// 获取所有活跃的路由记录列表
getRoutes() {
return this.matcher.getRoutes();
}
// 动态添加路由(添加一条新路由规则)
addRoute(parentOrRoute, route) {
this.matcher.addRoute(parentOrRoute, route);
}
// 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
addRoutes(routes) {
this.matcher.addRoutes(routes);
}
// 导航到新url,向 history栈添加一条新访问记录
push(location) {
this.history.push(location);
}
// 在 history 记录中向前或者后退多少步
go(n) {
this.history.go(n);
}
// 导航到新url,替换 history 栈中当前记录
replace(location, onComplete) {
this.history.replace(location, onComplete);
}
// 导航回退一步
back() {
this.history.go(-1);
}
}
VueRouter.install = install;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
install.js
部分代码
Vue.mixin({
// Vue创建前钩子,此生命周期$options已挂载完成
beforeCreate() {
// 通过判断组件实例this.$options有无router属性来判断是否为根实例
// 只有根实例初始化时我们挂载了VueRouter实例router(main.js中New Vue({router})时)
if (this.$options.router) {
this._routerRoot = this;
// 在 Vue 根实例添加 _router 属性( VueRouter 实例)
this._router = this.$options.router;
// 调用VueRouter实例初始化方法
// _router即VueRouter实,此处this即Vue根实例
this._router.init(this);
// 把 ($route <=> _route) 处理为响应式的
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else {
// 为每个组件实例定义_routerRoot,回溯查找_routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
}
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vue-router
将实例以递归的形式挂载到根实例上,这样就避免了创建了多个实例对象,减少性能消耗- 调用
Vue.util.defineReactive
响应式代理this._route达到监听页面变化this._router.init(this);
其实调用的是router
原型上init方法
# 不同路由模式监听机制
- 不同与传统页面渲染机制,vuejs以单页面应用的形式渲染(single page application)
- 单页面渲染的优势,首页资源加载比较多,但是用户跳转路由页面是不刷新的,这主要归功于html5提供的hash模式和history模式
- 通过监听hash或者history变化响应页面更新
base.js
hash.js
html5.js
暴露的几个常用的api
# router-link、router-view
以全局组件形式挂载
router-link
# $router、 $route
# 最后
vue-router源码很多,源码阅读、手写源码等只是你学习这其中的思想,更多的是思考为什么要这么写 欢迎大家留言交流