一看就懂的router路由实现原理

一、什么是前端路由


  • 路由这个概念最先是后端出现的。在以前用模板引擎开发页面时,经常会看到这样
        http://www.xxx.com/login

        http://www.xxx.com/register

        http://www.xxx.com/home
        
复制代码
  • 大致流程可以看成这样:
  1. 浏览器发出请求

  2. 服务器监听到80端口(或443)有请求过来,并解析url路径

  3. 根据服务器的路由配置,返回相应信息(可以是 html 字串,也可以是 json 数据,图片等)

    1. 浏览器根据数据包的 Content-Type 来决定如何解析数据
  • 后端路由简单来说路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。而前端路由则是在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。

二、前端路由模式

  • 前端路由有两种模式:分别为 hash模式history模式

1. hash 模式

随着 ajax 的流行,异步数据请求交互运行在不刷新浏览器的情况下进行。而异步交互体验的更高级版本就是 SPA —— 单页应用。单页应用不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。

类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。在 2014 年之前,大家是通过 hash 来实现路由,url hash 就是类似于:

        http://www.xxx.com/#/login

        http://www.xxx.com/#/home
复制代码

这种 #。后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作:

2. history 模式

14年后,因为HTML5标准发布。多了两个 API,pushStatereplaceState,通过这两个 API 可以改变 url 地址且不会发送请求。同时还有popstate 事件。通过这些就能用另一种方式来实现前端路由了,但原理都是跟 hash 实现相同的。用了 HTML5 的实现,单页路由的 url 就不会多出一个#,变得更加美观。但因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。

三、如何实现前端路由?

  • 要实现前端路由,需要解决两个核心

    1. 如何改变 URL 却不引起页面刷新?

    2. 如何检测 URL 变化了?

下面分别使用 hashhistory 两种实现方式回答上面的两个核心问题。

1. hash 实现

hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新通过 hashchange 事件监听 URL 的变化。

  • 改变 URL 的方式只有这几种:

    1. 通过浏览器前进后退改变 URL

    2. 通过 a 标签改变 URL

    3. 通过window.location改变URL

2. history 实现

history 提供了 pushStatereplaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。

  • history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:

    1. 通过浏览器前进后退改变 URL 时会触发 popstate 事件

    2. 通过pushState/replaceState或 a 标签改变 URL 不会触发 popstate 事件。

    3. 好在我们可以拦截 pushState/replaceState的调用和 a 标签的点击事件来检测 URL 变化

    4. 通过js 调用 historybackgo,**forward **方法课触发该事件

所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。

四、原生js实现前端路由示例

1. hash实现

<!DOCTYPE html>
<html lang="en">
<body>
<ul>
    <ul>
        <!-- 定义路由 -->
        <li><a href="#/home">home</a></li>
        <li><a href="#/about">about</a></li>

        <!-- 渲染路由对应的 UI -->
        <div id="routeView"></div>
    </ul>
</ul>
</body>
<script>
    let routerView = routeView
    window.addEventListener('hashchange', ()=>{
        let hash = location.hash;
        routerView.innerHTML = hash
    })
    window.addEventListener('DOMContentLoaded', ()=>{
        if(!location.hash){//如果不存在hash值,那么重定向到#/
            location.hash="/"
        }else{//如果存在hash值,那就渲染对应UI
            let hash = location.hash;
            routerView.innerHTML = hash
        }
    })
</script>
</html>
复制代码
  • 解释下上面代码,其实很简单:
  1. 我们通过a标签的href属性来改变URL的hash值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入window.location赋值来改变hash)

  2. 我们监听hashchange事件。一旦事件触发,就改变routerView的内容,若是在vue中,这改变的应当是router-view这个组件的内容

  3. 为何又监听了load事件?这时应为页面第一次加载完不会触发 hashchange,因而用load事件来监听hash值,再将视图渲染成对应的内容

2. history 实现

<!DOCTYPE html>
<html lang="en">
<body>
<ul>
    <ul>
        <li><a href='/home'>home</a></li>
        <li><a href='/about'>about</a></li>

        <div id="routeView"></div>
    </ul>
</ul>
</body>
<script>
    let routerView = routeView
    window.addEventListener('DOMContentLoaded', onLoad)
    window.addEventListener('popstate', ()=>{
        routerView.innerHTML = location.pathname
    })
    function onLoad () {
        routerView.innerHTML = location.pathname
        var linkList = document.querySelectorAll('a[href]')
        linkList.forEach(el => el.addEventListener('click', function (e) {
            e.preventDefault()
            history.pushState(null, '', el.getAttribute('href'))
            routerView.innerHTML = location.pathname
        }))
    }

</script>
</html>

复制代码

解释下上面代码:

  1. 我们通过a标签的href属性来改变URL的path值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入history.go,back,forward赋值来触发popState事件)。这里需要注意的就是,当改变path值时,默认会触发页面的跳转,所以需要拦截 a 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。

  2. 我们监听popState事件。一旦事件触发,就改变routerView的内容。load事件则是一样的

  • 有个问题:hash模式,也可以用history.go,back,forward来触发hashchange事件吗?

    A:也是可以的。因为不管什么模式,浏览器为保存记录都会有一个栈。

哈哈,到这里就差不多,为了搞清楚它的原理也参考了很多文档,东拼西凑的尽量以容易理解的方式把它整理了一下。希望对你们有所帮助,当然,如果本文有什么不妥的地方也欢迎指出。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享