简易路由实现——(history路由)

前言

上篇文章讲述了 简易路由实现——(hash路由)的简单实现,本文续讲 history 路由的实现html

话很少说,先上 demo&& 源码&& 工程文件(htmlRouter文件夹下)前端

history 路由原理

History 是 HTML5 新增的标准,对比 hash 它的展现更加优雅,但低版本 ie 还有兼容问题。vue

History 的 pushState,replacestate 方法能够添加修改历史记录且不会发送新的服务器请求,所以能够利用此特性实现前端路由跳转。node

History 的 go ,back, forward 方法能够实现跳转,后退,前进功能,还有 popstate 事件能够监听到记录变动。webpack

页面结构

因为 a 标签 <a href="/monday"> 会致使页面页面跳转,所以页面结构需改写一番,使用插件方法进行路由跳转git

<ul class="nav-list">
  <li class="nav-item"><a onclick="router.push({name: 'monday'})">周一</a></li>
  <li class="nav-item"><a onclick="router.push({name: 'tuesday', query: {name: 'suporka', age: '26'}})">周二</a></li>
  <li class="nav-item"><a onclick="router.push({path: '/wednesday'})">周三</a></li>
  <li class="nav-item"><a onclick="router.push({path: '/thursday', query: {name: 'suporka', age: '20'}})">周四</a></li>
  <li class="nav-item"><a onclick="router.replace({name: 'friday'})">周五</a></li>
</ul>
复制代码

实现 history 路由

init()

在 MDN 上,是这样介绍 popstate 的github

当活动历史记录条目更改时,将触发 popstate 事件。若是被激活的历史记录条目是经过对 history.pushState()的调用建立的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。web

须要注意的是调用 history.pushState()history.replaceState() 不会触发 popstate 事件。只有在作出浏览器动做时,才会触发该事件,如用户点击浏览器的回退按钮(或者在 Javascript 代码中调用 history.back() 或者 history.forward() 方法)算法

不一样的浏览器在加载页面时处理 popstate 事件的形式存在差别。页面加载时 ChromeSafari 一般会触发 popstate 事件,但 Firefox 则不会。vue-router

所以在 history 路由的初始化方法中,须要对 popstate 和 load 事件进行监听

export default class HistoryRouter extends RouterParent {
    constructor(routerConfig) {
        super(routerConfig);
    }

    init() {
        // refresh 实现对应组件和当前路由绑定显示
        // bind(this) 传入此实例对象,不然this指向有问题
        window.addEventListener('popstate', this.refresh.bind(this), false);
        window.addEventListener('load', this.refresh.bind(this), false);
    }
}
复制代码

refresh()

与 hash 路由实现一致,这里是对组件控制显示隐藏,只不过在这里能够直接使用 history 的功能, 不用本身创建 routeHistory 来控制跳转

refresh() {
    let path = window.location.pathname,
        currentComponentName = '',
        nodeList = document.querySelectorAll('[data-component-name]');
    // 找出当前路由的名称
    for (let i = 0; i < this._routes.length; i++) {
        if (this._routes[i].path === path) {
            currentComponentName = this._routes[i].name;
            break;
        }
    }
    // 根据当前路由的名称显示对应的组件
    nodeList.forEach(item => {
        if (item.dataset.componentName === currentComponentName) {
            item.style.display = 'block';
        } else {
            item.style.display = 'none';
        }
    });
}
复制代码

back() && front()

后退前进直接调用 history 的 api 便可,此时会触发 popstate 事件调用 refresh 方法渲染页面

back() {
    window.history.back();
}
front() {
    window.history.forward();
}
复制代码

push(option)

在vue-router中,能够经过 path, name 修改当前路由,而且能够携带 query 参数 所以优先判断 path, 若是有 path, 则直接调用 pushState 添加历史记录; 没有 path, 则根据 name 从 routes 中找出 path, 再调用 pushState 添加历史记录。由于 history.pushState()history.replaceState() 不会触发 popstate,所以咱们须要手动调用一下 refresh 方法

push(option) {
    if (option.path) {
      // 绑定this指向,使函数能够调用类的方法
      pushHistory.call(this, option.path,option.query);
    } else if (option.name) {
        let routePath = '';
        // 根据路由名称找路由path
        for (let i = 0; i < this._routes.length; i++) {
            if (this._routes[i].name === option.name) {
                routePath = this._routes[i].path;
                break;
            }
        }
        if (!routePath) {
            error('组件名称不存在');
        } else {
            pushHistory.call(this, routePath, option.query);
        }
    }
}

// 路由跳转
function pushHistory(routePath, query) {
    let path = getTargetPath(routePath, query);
    if (path !== window.location.pathname) {
        window.history.pushState(path, '', path);
        this.refresh();
    }
}

function error(message) {
    typeof console !== 'undefined' && console.error(`[html-router] ${message}`);
}

// 获取即将跳转的路径
function getTargetPath(path, query) {
    if (!query) return path;
    let str = '';
    for (let i in query) {
        str += '&' + i + '=' + query[i];
    }
    return path + '?' + str.slice(1);
}

复制代码

replace(option)

replace 和 push 的逻辑基本一致,只是调用的不是 pushState,而是 replaceState 方法。所以对 push 方法改造一下,使其兼容 replace

replace(option) {
    // 表示当前处于replace
    this.replaceRouter = true;
    this.push(option);
}
push(option) {
    if (option.path) {
        pushHistory.call(this, option.path, option.query, this.replaceRouter);
    } else if (option.name) {
        let routePath = '';
        // 根据路由名称找路由path
        for (let i = 0; i < this._routes.length; i++) {
            if (this._routes[i].name === option.name) {
                routePath = this._routes[i].path;
                break;
            }
        }
        if (!routePath) {
            error('组件名称不存在');
        } else {
            pushHistory.call(this, routePath, option.query, this.replaceRouter);
        }
    }
}

// 改写路由跳转
function pushHistory(routePath, query, replace) {
    let path = getTargetPath(routePath, query);
    if (path !== window.location.pathname) {
        if (replace) {
            window.history.replaceState(path, '', path);
            this.replaceRouter = false;
        } else window.history.pushState(path, '', path);
        this.refresh();
    }
}
复制代码

demo 测试

测试代码就不写了,与前文 hash 路由一致,效果以下:

可是在这里发现一个问题,当处于某个路由时,刷新页面,会出现下面这种状况

一刷新就会出现404,在 vue-router官方文档 中也有介绍,开启 history 须要服务端支持!

当你使用 history 模式时,URL 就像正常的 url,例如 yoursite.com/user/id,也好看…

不过这种模式要玩好,还须要后台配置支持。由于咱们的应用是个单页客户端应用,若是后台没有正确的配置,当用户在浏览器直接访问 oursite.com/user/id 就会返回 404,这就很差看了。

因此呢,你要在服务端增长一个覆盖全部状况的候选资源:若是 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

以上即是《简易路由实现》的完整内容,更多文章可进入个人专栏查看,感谢您的观看

更多推荐

前端进阶小书(advanced_front_end)

前端每日一题(daily-question)

webpack4 搭建 Vue 应用(createVue)

Canvas 进阶(一)二维码的生成与扫码识别

Canvas 进阶(二)写一个生成带logo的二维码npm插件

Canvas 进阶(三)ts + canvas 重写”辨色“小游戏

Canvas 进阶(四)实现一个“刮刮乐”游戏

Canvas 进阶(五)实现图片滤镜效果

VUI建立日志(一)——图片懒加载指令的实现

VUI建立日志(二)——防抖节流组件的实现

前端算法题目解析(一)

前端算法题目解析(二)

相关文章
相关标签/搜索