乐趣区

关于vue.js:Vue路由实现三hash跳转原理

在 new vueRouter 的时候咱们能够传入一个 mode 属性,他能够接管三个值:hash/history/abstract

hash 和 history 的区别

history 的门路更好看一点 比方 http://yoursite.com/user/id,history 是基于 pushState() 来实现 URL 跳转而毋庸从新加载页面。然而强制刷新还是会有问题(服务端来解决这个问题),所以 history 模式须要后端人员配合应用。

hash 的门路会带有#, 比方http://yoursite.com#/user/id

HashHistory

class VueRouter{constructor(options){this.matcher = createMatcher(options.routes || []);
// 这里为了解说 hash 模式 所以就不进行判断用户传进来的是哪种模式了
        this.history = new HashHistory(this);//this vue-router 的实例
        }
}

源码这里创立了一个基类咱们这里和源码对立,这个基类封装了三种模式专用的办法和属性, 那么咱们在这里创立一个 HashHistory 和基类 History

import History from './base'
// hash 路由
export default class HashHistory extends History{constructor(router){super(router); // 继承调用父类 等于 call
    }
}
// 路由的基类
export default class History {constructor(router){this.router = router;}
}

如果是 hash 路由, 关上网站如果没有 hash 默认应该增加#/

import History from './base';
function ensureSlash(){if(window.location.hash){return}
    window.location.hash = '/'
}
export default class HashHistory extends History{constructor(router){super(router);
        ensureSlash(); // 确保有 hash}
}

再看一下初始化的逻辑(下面的 router.init 函数)

init(app){
        const history = this.history;
        // 初始化时,应该先拿到以后门路,进行匹配逻辑

        // 让路由零碎适度到某个门路
        const setupHashListener = ()=> {history.setupListener(); // 监听门路变动
        }
        history.transitionTo( // 父类提供办法负责跳转
            history.getCurrentLocation(), // 子类获取对应的门路
            // 跳转胜利后注册门路监听,为视图更新做筹备
            setupHashListener
        )
}

这里咱们要别离实现 transitionTo(基类办法)、getCurrentLocationsetupListener

getCurrentLocation 实现

function getHash(){return window.location.hash.slice(1);
}
export default class HashHistory extends History{
    // ...
    getCurrentLocation(){return getHash();
    }
}

setupListener实现

export default class HashHistory extends History{
    // ...
    setupListener(){window.addEventListener('hashchange', ()=> {
            // 依据以后 hash 值 适度到对应门路
            this.transitionTo(getHash());
        })
    }
}

TransitionTo实现

export function createRoute(record, location) {// {path:'/',matched:[record,record]}
    let res = [];
    if (record) { // 如果有记录 
        while(record){res.unshift(record); // 就将以后记录的父亲放到后面
            record = record.parent
        }
    }
    return {
        ...location,
        matched: res
    }
}
export default class History {constructor(router) {
        this.router = router;
        // 依据记录和门路返回对象, 稍后会用于 router-view 的匹配
        this.current = createRoute(null, {path: '/'})
    }
    // 外围逻辑
    transitionTo(location, onComplete) {
        // 去匹配门路
        let route = this.router.match(location);
        // 雷同门路不用过渡
        if(
            location === route.path && 
            route.matched.length === this.current.matched.length){return}
        // 更新路由并且上面会提到扭转根实例上的_route 属性
        this.updateRoute(route)
        onComplete && onComplete();}
}
export default class VueRouter{
    // ...
    // 做一个代理
    match(location){return this.matcher.match(location);
    }
}

macth 办法

function match(location){ // 稍后依据门路找到对应的记录
    let record = pathMap[location]
    if (record) { // 依据记录创立对应的路由
    // 参数:/about/a:{path:xx,component...},path:'/about/a'
        return createRoute(record,{path:location})
    }
    // 找不到则返回空匹配
    return createRoute(null, {path: location})
}

咱们不难发现门路变动时都会更改 current 属性,咱们能够把 current 属性变成响应式的,每次 current 变动刷新视图即可
在 install 办法中

install(Vue) {
    Vue.mixin({ // 给所有组件的生命周期都减少 beforeCreate 办法
        beforeCreate() {if (this.$options.router) { 
            // 调用 Vue 类中双向数据绑定办法
            Vue.util.defineReactive(this,'_route',this._router.history.current);
            } 
        }
    });
 // $route 和 $router 办法 这两个办法仅仅是 vue 中最常见的代理 仅仅是为了更加不便
    Object.defineProperty(Vue.prototype,'$route',{ // 每个实例都能够获取到 $route 属性
        get(){return this._routerRoot._route;// 下面刚进行双向数据绑定的}
    });
    Object.defineProperty(Vue.prototype,'$router',{ // 每个实例都能够获取 router 实例
        get(){return this._routerRoot._router;}
    })
    }

切换路由每次初始化时都须要调用更新 _route 的办法,因为 install 的时候把 _route 进行双向数据绑定,刚进来是没有 this._router.history.current 的,通过公布订阅形式来进行订阅和更新操作;在 init 办法中减少监听函数

history.listen((route) => { // 须要更新_route 属性,出入一个函数
    app._route = route
});
export default class History {constructor(router) {
        // ...
        this.cb = null;
    }
    listen(cb){this.cb = cb; // 注册函数}
    updateRoute(route){
        this.current =route;
        this.cb && this.cb(route); // 更新 current 后 更新_route 属性
    }
}
退出移动版