# 目录结构
├── component
│ ├── router-link.js
│ ├── router-view.js
├── history
│ ├── base.js
│ ├── hash.js
│ └── html5.js
├── create-matcher.js
├── create-route-map.js
├── index.js
└── install.js
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# index
import { install } from './install.js';
import createMatcher from './create-matcher';
import HashHistory from './history/hash';
import HTML5History from './history/html5';
class VueRouter {
constructor(options) {
this.matcher = createMatcher(options.routes); // match addRoute
this.mode = options.mode || 'hash';
switch (this.mode) {
case 'hash':
this.history = new HashHistory(this);
break;
case 'history':
this.history = new HTML5History(this);
break;
}
}
match(path) {
return this.matcher.match(path);
}
init(vueRoot) {
// 匹配出跳转后路径对应的组件
const history = this.history;
//页面初始化跳转一次
history.transitionTo(history.getLocation(), () => {
history.setUpListener();
});
//如果current发生变化,就重新给$route(_route)赋值
history.listen(route => {
vueRoot._route = route;
});
}
//路由跳转
push(path) {
this.history.transitionTo(path, () => {
this.history.push(path);
});
}
replace(path) {
this.history.transitionTo(path, () => {
this.history.replace(path);
});
}
}
VueRouter.install = install;
export default VueRouter;
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
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
# install
import RouterLink from './components/link';
import RouterView from './components/view';
export let Vue; //暴露Vue,保证Vue版本一致
/* 初始化$router $route router-link router-view */
export function install(_Vue) {
if (install.installed) return;
Vue = _Vue;
install.installed = true;
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
//根实例
//给根实例添加2个属性,调用VueRouter的init,让每个子组件都能拿到_routerRoot和_routerRoot._router
this._router = this.$options.router;
this._routerRoot = this;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else {
//其他组件实例
this._routerRoot = this.$parent && this.$parent._routerRoot;
}
}
});
// $router 路由实例
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router;
}
});
// $route 当前匹配到的路由记录 {path , matched:[] , params,query,...}
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route;
}
});
Vue.component('router-link', RouterLink);
Vue.component('router-view', RouterView);
}
if (window && window.vue) {
install(window.vue);
}
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
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
# createMatcher.js
import createRouteMap from './create-route-map';
export default function createMatcher(routes) {
// / home
// /about about
// /about/a aboutA
// /about/b aboutB
let { pathMap } = createRouteMap(routes); // 创建一个 路径和记录的映射表
// 在原来的基础上继续添加路由
function addRoutes(routes) {
createRouteMap(routes, pathMap);
}
function match(path) {
const record = pathMap[path];
return createRoute(record, { path });
}
return {
match,
addRoute: addRoutes,
addRoutes //已废弃,使用router.addRoute
};
}
export const createRoute = (record, { path }) => {
let matched = [];
if (record) {
while (record) {
matched.unshift(record);
record = record.parent; // 一层层的向上找
}
}
return {
path,
matched
};
};
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
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
# createRouteMap.js
export default function createRouteMap(routes, pathMap = {}) {
routes.forEach(route => {
addRouteRecord(route, pathMap);
});
return { pathMap };
}
function addRouteRecord(route, pathMap, parent) {
let path = parent ? parent.path + route.path : route.path;
const record = {
path,
component: route.component,
parent
};
pathMap[path] = record;
if (route.children)
route.children.forEach(childRoute =>
addRouteRecord(childRoute, pathMap, record)
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# base.js
import { createRoute } from '../create-matcher';
export default class History {
constructor(vueRouter) {
this.router = vueRouter;
//将current属性变成响应式的,如果在渲染router-view时候用到了这个current,等会current变化了就可以重新刷新视图
this.current = createRoute(null, { path: '/' });
}
transitionTo(path, callback) {
//根据路径匹配组件及父组件
let record = this.router.match(path);
// hash模式下会走2次,因为调用transitionTo之后改变hash,监听hash变化又走了1次。所以加个判断,相同路径不再跳转
if (
path === this.current.path &&
record.matched.length === this.current.matched.length
) {
return;
}
this.current = record; //改变current
this.cb && this.cb(this.current); //还有改变_route,使视图更新
callback && callback();
}
listen(cb) {
this.cb = cb;
}
}
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
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
# hash.js
import History from './base';
function getHash() {
return window.location.hash.slice(1);
}
export default class HashHistory extends History {
constructor(vueRouter) {
super(vueRouter);
//初始化检测hash值 给出默认路径
if (!window.location.hash) window.location.hash = '/';
}
getLocation() {
return getHash();
}
setUpListener() {
window.addEventListener('hashchange', e => {
//hash变化 组件渲染
this.transitionTo(getHash());
});
}
//改变路径
push(hash) {
window.location.hash = hash;
}
replace(hash) {
function getUrl(path) {
const href = window.location.href;
const i = href.indexOf('#');
const base = i >= 0 ? href.slice(0, i) : href;
return `${base}#${path}`;
}
window.location.replace(getUrl(hash));
}
}
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
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
# html5.js
import History from './base';
export default class HTML5History extends History {
constructor(router) {
super(router);
}
getLocation() {
return window.location.pathname;
}
setUpListener() {
window.addEventListener('popstate', () => {
//hash变化 组件渲染
this.transitionTo(this.getLocation());
});
}
//改变路径
push(path) {
window.history.pushState({}, null, path);
}
replace(path) {
window.history.replaceState({}, null, path);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 全局组件 router-link
export default {
functional: true,
props: {
to: {
type: String,
required: true
},
replace: {
type: Boolean,
required: false
}
},
render(h, { props, slots, parent }) {
function click() {
props.replace
? parent.$router.replace(props.to)
: parent.$router.push(props.to);
}
return <a onClick={click}>{slots().default}</a>;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 全局组件 router-view
export default {
functional: true,
render(h, { parent, data, props, children }) {
data.routerView = true; // 标识
let route = parent.$route; // $route === _route === current
let depth = 0;
while (parent) {
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++;
}
parent = parent.$parent;
}
let record = route.matched[depth];
if (!record) {
return h();
}
return h(record.component, data);
}
};
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