实现一个hash路由,以及history路由

先验

最近在看vue-router源码,先了解一下两种前端路由模式的具体实现。

hash路由:监听url的hash部分变化,渲染相应组件,hash是完全的前端路由,不会向后端发起请求。

hash路由的变化需要监听hashchange事件,事件对象里有oldURL、newURL属性表示变化前后的url

window.addEventListener("hashchange", function (e) {
    console.log(e.oldURL, e.newURL);
})

封装一个函数,从获得的url里面,需要提取出hash部分

function getHashFromURL(url) {
    let index = url.indexOf("#");
    if (index >= 0) return url.slice(index + 1);
    return "/";
}

当hash地址发生变化,需要重新获取当前hash,并触发回调

function refresh(event) {
    let newhash = "";
    let oldhash = null;
    if (event.newURL) {
        oldhash = this.getHashFromURL(event.oldURL);
        newhash = this.getHashFromURL(event.newURL);
        this.emit("change", oldhash, newhash);
    } else {
        // 第一次进入页面的时候不触发hashchange事件,所以事件对象里没有newURL属性
        newhash = this.getHashFromURL(window.location.hash);
    }
}

订阅事件

const handler = {};
function on(eventName,callback){
    handler[eventName] = callback;
}

发布事件

function emit(eventName,...args){
    let callback=handler[eventName];
    if(callback) callback(...args);
}

创建HashRouter类

class HashRouter {
    // 1 类里面要保存当前的hash地址,以及注册的回调
    currentHash = "";
    handler = {};

    // 4 刚进入页面触发load事件,后续hash地址变化才会触发hashchange事件
    constructor(){
        this.refresh=this.refresh.bind(this);// 避免因为事件监听触发回调改变了this
        window.addEventListener("load",this.refresh);
        window.addEventListener("hashchange",this.refresh);
    }

    // 2 从url获取hash
    getHashFromURL(url) {
        let index = url.indexOf("#");
        if (index >= 0) return url.slice(index + 1);
        return "/";
    }

    // 3 每次hash地址变化需要重新获取hash,并emit触发回调(这个回调里面应该去渲染对应组件)
    refresh(event) {
        let newhash = "";
        let oldhash = null;
        if (event.newURL) {
            oldhash = this.getHashFromURL(event.oldURL);
            newhash = this.getHashFromURL(event.newURL);
            this.emit("change", oldhash, newhash);
        } else {
            // 第一次进入页面的时候不触发hashchange事件,所以事件对象里没有newURL属性
            newhash = this.getHashFromURL(window.location.hash);
        }
        this.currentHash = newhash;
    }

    // 发布事件
    emit(eventName,...args){
        let callback=this.handler[eventName];
        if(callback) callback(...args);
    }

    // 订阅事件
    on(eventName,callback){
        this.handler[eventName] = callback;
    }
}

简单测试结果----OK

const router = new HashRouter();
router.on("change",(oldhash,newhash)=>{
    console.log(oldhash,newhash);
})

举个稍微完整点的例子

路由规则:

const routes = [
    {path: '/',name: 'home',component: <Home />,},
    {path: '/about',name: 'about',component: <About />,},
    {path: '*',name: '404',component: <NotFound404 />,},
];

创建router实例,封装回调函数,注册监听事件change

const router = new HashRouter();

// hash变化的回调函数---找到对应组件,重新渲染
const callback = (oldHash, newHash) => {
    let route = null;
    // 匹配路由
    for (let item of routes) {
        if (newHash === item.path) {
            route = item;
            break;
        }
    }
    // 若没有匹配到,则使用最后一个路由
    if (!route) {
        route = routes[routes.length - 1];
    }
    // 渲染当前的组件
    ReactDOM.render(route.component, document.getElementById('app'));
}

// 监听change事件
router.on('change', callback);

history路由

history历史记录管理是HTML5新增的API,常见的方法:window.history.

1)back():回到历史记录中的上一条路由
2)forward():如果有的话前进到下一条路由
3)go(n):跳转到任意一条记录
4)pushState(obj,title,url):跳转到指定同源url,并向历史记录中添加
5)replaceState(obj,title.url):跳转到指定同源url,替换掉当前的url记录

这些方法都只是单纯的替换url,1)不会向服务器发起请求,2)页面内容不变,页面的内容变化需要通过监听url变化事件触发回调

1) 不会向服务器发起请求:history只是单纯替换url。但是一刷新还是会向服务器发起请求,所以需要服务端也做一些配置,不然没找到对应的资源就会返回404.

2)页面内容想变化:要监听 popState 事件,但是只有前三种方式跳转才会触发这个事件,pushStatereplaceState这两种行为就需要通过dispatchEvent添加事件

const listener = function (type) {
    var orig = history[type];
    return function () {
    	// 规范
        var rv = orig.apply(this, arguments);
        // 添加一层事件
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
window.history.pushState = listener('pushState');
window.history.replaceState = listener('replaceState');

所以history类事件监听的初始化应该是

window.addEventListener('load', this.refresh, false);
window.addEventListener('popstate', this.refresh, false);
window.addEventListener('pushState', this.refresh, false);
window.addEventListener('replaceState', this.refresh, false);

其他的和hash模式差不多

class HistoryRouter {
    // 保存当前的url和注册的回调
    currentUrl = '';
    handlers = {};

    constructor() {
        this.refresh = this.refresh.bind(this);
        this.addStateListener();
        window.addEventListener('load', this.refresh, false);
        window.addEventListener('popstate', this.refresh, false);
        window.addEventListener('pushState', this.refresh, false);
        window.addEventListener('replaceState', this.refresh, false);
    }

    // 添加自定义事件
    addStateListener() {
        const listener = function (type) {
            var orig = history[type];
            return function () {
                var rv = orig.apply(this, arguments);
                var e = new Event(type);
                e.arguments = arguments;
                window.dispatchEvent(e);
                return rv;
            };
        };
        // 因为这两个行为不会触发 popState
        window.history.pushState = listener('pushState');
        window.history.replaceState = listener('replaceState');
    }

    // 获取当前url并触发回调的过程
    refresh(event) {
        this.currentUrl = location.pathname;
        this.emit('change', location.pathname);
    }

    on(evName, listener) {
        this.handlers[evName] = listener;
    }
    emit(evName, ...args) {
        const handler = this.handlers[evName];
        if (handler) {
            handler(...args);
        }
    }
}

const router = new HistoryRouter();
router.on('change', function (curUrl) {
    console.log(curUrl);
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值