在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属性    }}