Vue路由实现(三):hash跳转原理

在new vueRouter的时候我们可以传入一个mode属性,他可以接收三个值:hash/history/abstract
Vue路由实现(三):hash跳转原理_第1张图片

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

你可能感兴趣的:(Vue路由实现(三):hash跳转原理)