location对象含有很多的属性,其中我们此次使用到的是 hash,接下来我们需要熟悉一下hash的用法。
<a href="#/index">首页a>
<a href="#/app">appa>
<div id="app">div>
<script type="text/javascript">
window.addEventListener('load', ()=>{
// 初始化url的hash模式,得到url地址,更改视图数据
let hash = location.hash;
hash?'':location.hash='/';
app.innerHTML = hash.slice(1);
})
window.addEventListener('hashchange',()=>{
// 获取到当前的值,改变视图
let hash = location.hash.slice(1);
app.innerHTML = hash;
})
script>
注意:
我们需要注册两个组件,分别是vue-router和vue-link。两者之间通过VueRouter做调度,当vue-link或者其他方式改变了VueRouter的history属性(记录当前路由信息),那么就会触发Vue的监听,然后通知router-view来更新当前路由地址所对应的组件视图。
在每一个组件上都有
$route路由信息,
$router路由方法
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = (function () {
let res = {};
options.routes.forEach(val=>{
res[val.path] = val.component;
})
return res;
})();
this.history = {
current: null
};
// 初始化VueRouter
this.init();
}
// 初始化函数
init() {
if (this.mode === 'hash') {
// 初始化地址
// console.log(location.hash);
location.hash ? '' : location.hash = '/';
// 当页面加载完成后将当前的地址保存
window.addEventListener('load', () => {
this.history.current = location.pathname;
console.log(1,location.pathname);
})
// 监听页面的跳转
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1);
console.log(2,location.hash);
})
} else {
// 如果为 / 说明第一次使用,否则用户改变了url,此时不做任何操作
location.pathname === '/' ? location.pathname = '/' : '';
window.addEventListener('load', () => {
this.history.current = location.pathname;
})
window.addEventListener('popstate', () => {
this.history.current = location.pathname;
})
}
}
分析:
初始化的操作并不是很难,我们在一开始便进行了练习。
我们在使用vue-router的时候都需要使用一个Vue.use(VueRouter)方法将vue-router注册到Vue中,use方法要求我们必须提供一个install方法。具体功能如下:
所谓的得到唯一实例是指。每个组件的this.$router和this.$route的得到的是同一个实例,具体的实现思路是递归,但是这个递归却决于Vue的渲染机制:父1->子1->孙1->父2->子2->孙2,所以代码如下:
// 确保每个组件得到的是同一个router对象
vue.mixin({
beforeCreate() {
// 将router挂载到根组件,因此必须确保接下来所有的this都为跟组件,即用this._root保存
if (this.$options && this.$options.router) {
this._root = this;
this._router = this.$options.router;
vue.util.defineReactive(this, 'xxx', this._root._router);
} else {
// 递归的思想,如果子组件没有$router属性,那么就去父组件去找,不断循环。
this._root = this.$parent._root;
}
// 拦截$route属性
Object.defineProperty(this, '$route', {
get() {
return {
current: this._root._router.history.current
}
}
})
// 拦截$router属性
Object.defineProperty(this, '$router', {
get() {
return this._root._router;
}
})
},
})
分析:
// 注册router-view组件
vue.component('router-view', {
render(h) {
let current = this._self._root._router.history.current;
let component = this._self._root._router.routes[current];
return h(component);
}
});
// 注册router-link组件
vue.component('router-link', {
props: ['to'],
render(h) {
return <a href={ this._root._router.mode === 'hash' ? `#${this.to}` : this.to }>{this.$slots.default}</a>;
}
});
分析:
以上基本完成了一个简易的vue-router,当然还存在很多的缺陷,希望读者见谅,在手写viue-router的过程中我也得到了一些心得体会,和读者进行分享。