在vueRouter中,主要分为两种模式,一种是hash,一种是history。
<a onclick="go('/about')">关于a>
<div id="html">
div>
function go(pathname){
// 通过history模式,页面不会刷新,只会往历史记录中增加历史记录
history.pushState({}, null, pathname);//第一个参数是传入的数据。第二个是名字,无实际意义。第三个是路径
html.innerHTML = pathname;
}
// 监听popstate事件,也就是监听历史记录改变,比如前进或后退,就会触发该事件
window.addEventListener('popstate', () => {
html.innerHTML = location.pathname;
})
new Vue({
el:'#app',
router,
render:(h) => h(App)
})
我们在vue中,经常用标签的模式去使用router-link/router-view,可见,router-link/router-view其实是两个组件。
class HistoryRoute{
constructor(){
this.current = null;
}
}
class VueRouter{
constructor(options){
this.mode = options.mode || 'hash';
this.routes = options.routes || [];
// 你传递的路由表是一个数组,改造成一个对象:key是路径,value是路径对应的组件{'/home':Home,'/about':About}
this.routesMap = this.createMap(this.routes);
// 路由中需要存放当前的路径,当前路径变化的时候,应该渲染对应的组建
// this.history = {current:null};// 默认是null 当然,我们最好用一个类来管理当前路径,这样方便拓展
this.history = new HistoryRoute();
this.init(); // 开始初始化操作
}
init(){
// 判断是什么模式,如果是hash应该怎么样,如果是history应该怎么样
// 如果是hash
if(this.mode === 'hash'){
// 先判断用户打开时有没有hash,没有就跳转到#/这样的路径
location.hash ? '' : location.hash = '/';
// 读取当前的路径,比如是/home,存到this.history中
//刚加载页面的时候,把路径放到this.history中
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1);
});
//当路由改变的时候,也要将路径存放到this.history中
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1);
})
}else{// 如果是history
// 先判断用户打开的时候有没有路径,没有的恶化就跳转到#/这样的路径
location.pathname ? '' : location.path = '/';
// 页面一加载就把路径放到this.histroy中,这个跟hash是一样的,唯一区别的是用pathname,不是hash而已
window.addEventListener('load', () => {
this.history.current = location.pathname;
});
//当浏览器前进后退的时候,要将对应的路径放到this.history中
window.addEventListener('popstate', () => {
this.history.current = location.pathname;
})
}
}
createMap(routes){
return routes.reduce((prev, next, index, arr) => {
prev[next.path] = next.component;
return prev;
},{})
}
}
// 使用Vue.use,会自动调用插件这个类的install方法
VueRouter.install = function(Vue, options){
console.log(Vue, 'install');
// 每个组件都有this.$router / this.$route
// 如何使得每个组建都有this.$router/this.$route,那就要用到Vue.minxin
Vue.mixin({
beforeCreate(){
// 获取组件的属性名称
if(this.$options && this.$options.router){// 说明这是根组件
this._root = this;//把当前实例挂在在_root上
this._router = this.$options.router;// 把router实例挂在在_router上
//observer方法
//如果history中的current属性变化,也会刷新视图
//this.xxx = this._router.histoury
Vue.util.defineReactive(this,'xxx',this._router.history)//深度劫持,去看MVVM的原理
}else{
// vue组件的渲染顺序 父->子->孙
this._root = this.$parent._root;
}
Object.defineProperty(this, '$router', {
get(){
return this._root._router;
}
});
Object.defineProperty(this, '$route', {
get(){
return {
//当前路由所在的值
current:this._root._router.history.current
};
}
});
}
});
Vue.component('router-link',{
props:{
to:String,
tag:String
},
methods:{
handleClick(){
let mode = this._self._root._router.mode;
if(mode === 'hash'){
location.hash = this.to;
}else{
location.pathname = this.to;
}
}
},
render(h){//这个h是createElement
//这里的this是什么?在render方法中,this是Proxy,如果你要拿到Vue实例(组件),那么就要用this._self
//要先取到模式,然后根据模式来对应返回
let mode = this._self._root._router.mode;
let tag = this.tag ? this.tag : 'a';
return <tag on-click={this.handleClick} href={mode === 'hash' ? `#${this.to}` : this.to}>{this.$slots.default}</tag>;
}
})
Vue.component('router-view',{// 根据当前的状态 current 路由表{'/about':About}
render(h){
//这里的this是什么?在render方法中,this是Proxy,如果你要拿到Vue实例(根组件),那么就要用this._self
//如何将current 做成动态的,current变化,对应的组件也应该跟着变化
//vue实现双向绑定 ,主要靠Object.defineProperty的get和set
let current = this._self._root._router.history.current;
let routeMap = this._self._root._router.routesMap;
return h(routeMap[current]);
}
})
}
export default VueRouter;