前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大前端开发中的路由管理之二:web篇

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

作者头像
QQ音乐技术团队
发布2021-11-08 11:10:06
1.5K0
发布2021-11-08 11:10:06
举报

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:

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对象来管理页面的回调,

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事件的监听,定义事件触发时的回调函数,

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);        }    }}

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

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);
<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接口了,但只是用于页面的跳转,比如:

history.go(-1);       // 后退一页history.go(2);        // 前进两页history.forward();     // 前进一页history.back();      // 后退一页

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

// 按指定的名称和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事件监听,

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标签的劫持,

// 全局监听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的实现,

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

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

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');
<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。

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研发感兴趣的同仁们一起交流学习起来吧!!!

↓   ↓   ↓

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯音乐技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 4、memory模式
  •         SPA的路由模式还有一种叫memory的模式,其特点是内容变化,但URL始终不变。由于其不符合上述的目标,所以这里只是简单介绍其实现原理。实现方式就是利用window.localstorage保存当前的路径,根据路径映射出页面内容。合适的使用场景比如react-native。
  • 5、 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档