大前端开发中的路由管理之二:web篇

2021-11-08 11:10:06 浏览数 (1)

1、Web路由需要实现的目标

        上一篇文章中我们谈到了SPA(Single-page application)的出现,但SPA的应用有个需要解决的问题,就是浏览器只加载记录了一个html,所以当刷新浏览器时js会重新执行,当前页面的内容便会丢失;页面跳转时浏览器不会向服务器发出新的页面请求,浏览器也就无法前进、后退页面。

        所以前端web路由需要实现以下目标:

      (1)能根据页面URL来获取不同的模块,但不发起新的页面请求;

      (2)能监听URL的变化。

        而hash和history这两种模式便是其实现原理。

2、 hash模式

        URL的hash属性是一个可读可写的字符串,该字符串是URL的锚部分(即#后面的部分)。例如http://abc.com/#fragment,fragment便是hash值。

        '#'是用来指导浏览器动作的,对服务器完全无用,其值的改变不会导致浏览器发起http请求,也不会引起页面的重载。但每次hash值的改变,都会在浏览器的访问历史栈里增加一个记录,使用'后退'键便能返回上一个位置。在H5的history模式出现之前,hash是前端路由的实现方式。

核心API:

代码语言:javascript复制
1、window.location.hash是个可读可写属性,读取时可以校验hash的变化,写入时可以不重载页面修改浏览器记录
2、onhashchange事件这是一个H5新增的事件,当#值发生变化时,就会触发这个事件。IE8 、Firefox 3.6 、Chrome 5 、Safari 4.0 支持该事件。实现方式如下:window.addEventListener('onhashchange', func, false);当浏览器不兼容时,可以用setInterval监控location.hash的变化。

核心功能的简单实现:

        首先要实现一个router对象来管理页面的回调,

代码语言:javascript复制
class HashRouter{    constructor(routeArr = []){        // 管理页面的回调        this.routers = {};        routeArr.forEach(item => this.register(item));    }    // 注册    register(item){        const {name, content} = item;        this.routers[name] = typeof content === 'function' ? content : function(){};    }}

        然后添加hashchange事件的监听,定义事件触发时的回调函数,

代码语言:javascript复制
class HashRouter{    constructor(routeArr = []){        // 管理页面的回调        this.routers = {};        routeArr.forEach(item => this.register(item));                     window.addEventListener('hashchange',this.load.bind(this),false);    }    // 注册    register(item){        const {name, content} = item;        this.routers[name] = typeof content === 'function' ? content : function(){};    }
    load(){        let hash = location.hash.slice(1),            handler = this.routers[hash];        // 执行注册的回调函数        try{            handler.apply(this);        }catch(e){            console.error(e);        }    }}

        最后添加上对象的初始化和页面内容,

代码语言:javascript复制
const container = document.getElementById('container');const routeArr = [{name: 'index', content: ()=> container.innerHTML = '这是首页'},{name: 'about', content: ()=> container.innerHTML = '这是关于页'},{name: 'detail', content: ()=> container.innerHTML = '这是详情页'}];cosnt router = new HashRouter(routeArr);
代码语言:javascript复制
<body>  <div id="header">    <a href="#index">index</a>    <a href="#about">about</a>    <a href="#detail">detail</a>  </div>  <div id="container"></div></body>

        当点击页面上的按钮时,页面内容便会变换,这样就基本介绍了hash模式下路由的实现原理。接下来介绍一下history模式。

3、 history模式

        history接口允许操作浏览器曾经在标签页或者框架里访问的会话历史记录。在H5之前其实存在history接口了,但只是用于页面的跳转,比如:

代码语言:javascript复制
history.go(-1);       // 后退一页history.go(2);        // 前进两页history.forward();     // 前进一页history.back();      // 后退一页

        在H5规范中引入了三个新的API,

代码语言:javascript复制
// 按指定的名称和URL(如果提供该参数)将数据push进会话历史栈history.pushState();// 按指定的数据,名称和URL(如果提供该参数),更新历史栈上最新的入口history.replaceState();// 返回当前状态对象history.state

        因为pushState和replaceState都可以改变URL的同时,不引起页面重载,所以history符合了目标一的条件。

   回顾hash模式,在hash被改变时会触发hashchange事件,而window上也有一个popstate事件。当活动历史记录条目更改时,将触发popstate事件。然而调用history.pushState()/history.replaceState()不会触发popstate事件,只有在做出浏览器动作时,才会触发该事件,比如用户点击浏览器的回退/前进按钮,或者在JS代码中调用history.back()/history.forward()方法。

 既然pushState和replaceState不会触发事件,那么我们需要换个思路来监听URL的变化。在单页应用中能改变URL的操作其实可以归为以下几种:

        1. 点击浏览器的前进或后退按钮;

        2. 点击 a 标签;

        3. 在JS代码中触发history.pushState函数;

        4. 在JS代码中触发history.replaceState函数;

        只要我们能控制以上的操作,就可以实现history模式的路由管理了。核心功能的简单实现如下:

        首先创建一个router对象,并添加popstate事件监听,

代码语言:javascript复制
class HistoryRouter{    constructor(routeArr = []){        // 管理页面的回调        this.routers = {};        routeArr.forEach(item => this.register(item));
        this.listenPopState();    }    // 注册    register(item){        const {path, content} = item;        this.routers[path] = typeof content === 'function' ? content : function(){};    }
    // 监听popstate事件,点击浏览器的前进后退按钮触发    listenPopState(){        window.addEventListener('popstate',(e)=>{                         let state = e.state || {},                path = state.path || '';                this.load(path);        },false)    }
    load(path){        let handler = this.routers[path];        // 执行注册的回调函数        try{            handler.apply(this);        }catch(e){            console.error(e);        }    }}

        然后添加对a标签的劫持,

代码语言:javascript复制
// 全局监听a标签的点击事件     listenALink(){    window.addEventListener('click',(e)=>{        let dom = e.target;        if(dom.tagName.toUpperCase() === 'A' && dom.getAttribute('href')){           e.preventDefault(); // 阻止原生事件           this.push(dom.getAttribute('href'));        }    },false)}

        再添加pushState和replaceState的实现,

代码语言:javascript复制
// 跳转到path     push(path){    history.pushState({path},null,path);    this.load(path); // 需要手动加载页面回调}// 替换为pathreplace(path){    history.replaceState({path},null,path);    this.load(path);}

        最后添加上对象的初始化和页面内容,

代码语言:javascript复制
const container = document.getElementById('container');
const routeArr = [{path: '/index', content: ()=> container.innerHTML = '这是首页'},{path: '/about', content: ()=> container.innerHTML = '这是关于页'},{path: '/detail', content: ()=> container.innerHTML = '这是详情页'}];cosnt router = new HistoryRouter(routeArr);
document.getElementById('push_btn').onclick = () => router.push('/detail');
document.getElementById('replace_btn').onclick = () => router.replace('/detail');
代码语言:javascript复制
<body>  <div id="header">    <a href="/index">index</a>    <a href="/about">about</a>    <a href="/detail">detail</a>  </div>  <div id="container"></div>  <div id="push_btn"></div>  <div id="replace_btn"></div></body>

        最后提一点,由于history是通过改变URL来进行路由的,当刷新页面时浏览器会向服务器访问当前地址,而服务器上不存在该页面,所以会出现404。为解决这个问题,我们需要修改web服务器的配置,让其在匹配不到页面时返回单页应用的页面。

4、memory模式

        SPA的路由模式还有一种叫memory的模式,其特点是内容变化,但URL始终不变。由于其不符合上述的目标,所以这里只是简单介绍其实现原理。实现方式就是利用window.localstorage保存当前的路径,根据路径映射出页面内容。合适的使用场景比如react-native。

代码语言:javascript复制
const routes = {  "/index": '这是首页',  "/about": '这是关于页',  "/detail": '这是详情页',};
const container = document.getElementById('container');
function route() {  let href = window.localStorage.getItem('cur-route');
  if (!href) {    href = "/index";  }    // 展示内容  container.innerHTML = routes[href];}
// 获取到所有class为link的a标签const allA = document.querySelectorAll('a.link');// 遍历a标签for (let a of allA) {  a.addEventListener('click', (e) => {    e.preventDefault();    const href = a.getAttribute('href');    window.localStorage.setItem('cur-route', href);    // 通知变化    onStateChange();  });}
function onStateChange() {  route();}
// 初始化route();

5、 结语

        下面总结一下几种方式的优缺点:

  • hash模式兼容性更好,且不需要服务器配合修改,但SEO不友好,并且hash模式的地址比较丑陋。
  • history模式对于SEO更友好,但需要服务端进行配置,并且IE8及以下不支持。
  • memeory模式的路由信息保存在内存中,浏览器的前进后退操作无效,更适合运用在单机应用中。

        以上便是web路由管理的几种常见实现方式,实现过程比较粗糙,希望能有助于大家在使用现代优秀的路由组件,如vue-router、react-router时能更好的运用在项目中。


        至此,我们了解到了web路由是如何去实现路由管理的,那么,就请期待我们下一篇文章《大前端开发中的路由管理之三:Android篇》吧,下篇文章将为大家揭秘Android端是如何去做路由管理的。

QQ音乐招聘 Android / iOS 客户端开发,点击左下方“查看原文”投递简历~

也可将简历发送至邮箱:tmezp@tencent.com


文末为大家推荐一个技术号《腾讯音乐天琴实验室》,TME天琴实验室致力于对业内前沿科技如AI等方向进行相关研发,持续推出新技术提升TME旗下QQ音乐等平台的音乐视听体验,对音视频相关AI研发感兴趣的同仁们一起交流学习起来吧!!!

↓   ↓   ↓

0 人点赞