搭建实验环境
首先github download下来https://github.com/vuejs/vue-router vue-router最新的代码,代码在src下,结构清晰,但不适合调试看中间的一些处理结果,所以新建一个文件夹,新建index.html,创建好vue-router官网上的demo实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</body>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
const Foo = {
template: '<div>foo</div>',
created() {
console.log('created');
},
methods: {
hello() {
console.log('hello');
}
}
}
const Bar = { template: '<div>bar</div>' }
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
const app = new Vue({
router
}).$mount('#app')
</script>
</html>
不同的是将官网上的CDN代码download下来到本地同文件夹下。
github下载的工程文件比较好阅读,单文件的vue-router.js可以一步步调试看数据结构,两个结合起来看。
matcher
我们打开VueRouter对象代码,有两个地方值得关注,一个是
this.matcher = createMatcher(options.routes || [], this);
另一个
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:
{
assert(false, ("invalid mode: " + mode));
}
}
可以看出一个是初始化matcher的createMatcher,打开createMatcher方法
createMatcher
我们的任务是看到vue-router是怎么处理存储路由结构的,所以应该是createRouteMap和addRoutes这个两个方法相关,但打开addRoutes,其实也是调用了createRouteMap,它是暴露给用户动态添加路由的方法。
so ,看createRouteMap,这个方法有个循环routes调用addRouteRecord,最终生成每个路由的数据结构
createRouteMap处理routers
追寻到这个方法,我们找到了每个routes的record数据结构
record结构
其中regex是通过给出的路由生成对应的正则表达式,还有对应的components,addRouteRecord还考虑了子路由的处理过程。但最终record通过对象map放到pathMap
pathMap
我们可以通过console打印出来
pathMap
而最终createRouteMap返回出来的是, 即VueRouter中matcher对象的数据结构。
createRouteMap返回结构
History
可以看到VueRouter如下生成History
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:
{
assert(false, ("invalid mode: " + mode));
}
}
分别是vue-router三种模式,具体的分别就不介绍了,官网上有。但这三个对象都是继承了基本的History类。在vue-router工程文件src/history/base.js,可以看到这个类的定义
History
简单的来说,我们操作路由router.push({ path: 'home' }),其实是在操作这个对象上的方法,而各个模式下的具体操作不同,例如hash模式下需要监听popstate和hashchange事件,调用之前matcher的match函数(具体调用在History的transitionTo中),匹配并更新window.history。具体的操作都在src/history下。
主要关注setupListeners -> transitionTo -> pushState这个几个方法的处理。可以看到最终操作window.history的是history.replaceState和history.pushState两个游览器API。
总结
通过matcher和History两个最重要的对象,VueRouter就完成了将我们定义好的路由结构转换成可以匹配的路由对象,并且添加事件监听,完成对相应路由的匹配并更新url历史。那么还有一个最重要的就是最终操作匹配到的组件并更新,这个下次写, 还有View和Link两个组件,其实也很简单,调用了Vue.util.defineReactive,定义了一个响应式对象:
Vue.util.defineReactive(this, '_route', this._router.history.current)
网友评论