前端路由实现原理

746 阅读4分钟

前端路由实现原理

SPA

说到前端路由,就不得不提一下SPA(单页面应用),所谓单页面,就是只有一张Web页面的应用,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载,也不会将控制转移到其他页面。而我们目前所熟知的VueReact等都属于SPA

实现前端路由需要解决的问题

  • 我们需要有方法可以让浏览器的地址栏发生改变,同时不能触发浏览器页面的刷新
  • 我们需要可以捕获到浏览器的地址栏的改变,并对这个改变进行监听
  • 在监听到地址栏的改变后,根据改变来修改页面的内容或展示新的组件

前端路由实现的两大方式

Hash模式

  1. Hash模式是一种把前端路由的路径用#号拼接在真实url后面的模式,当#号后面的路径发生变化时,浏览器并不会刷新页面,这是他天生就具有的功能,代替的是触发hashchange事件。所以我们很好的解决了第一个问题。
  2. 我们怎么监听浏览器url的改变呢,上面说道,在Hash模式中,路径的改变会触发hashchange事件,因此我们只需要在window上对hashchange事件进行监听,在回调函数中进行处理就可以。这样就解决了第二个问题。
  3. 当我们捕获到了url的改变,就可以进行处理了,在这里我们可以使用switch来模拟这样一个过程,我们可以通过location.hash来获取我们具体改变的hash值,然后进行case,一旦命中,就进行页面的修改或者展示新的组件。

代码实现如下:

Html部分如下

<ul>
    <li><a href="#/home">首页</a></li>
    <li><a href="#/about">关于</a></li>
 </ul>

<!-- 渲染对应的 UI -->
<div id="routerView">

</div>

JS部分

    let routerView = document.getElementById('routerView');
    window.addEventListener('load', onHashchange) // 在页面加载完毕后触发一次

    window.addEventListener('hashchange', onHashchange) // 每次触发hashchange事件触发一次
    
    // 控制渲染对应的 UI
    function onHashchange() {
      // console.log(location.hash);
      switch(location.hash) {
        case '#/home':
          routerView.innerHTML = 'Home';
          return
        case '#/about':
          routerView.innerHTML = 'About';
          return
        default:
          return
      }
    }
    routerView.innerHTML = 'Home';

实现效果如下图

chrome-capture-2023-6-7.gif

以上就是Hash路由的简单实现原理。

Hash的特点

  • hash变化会触发网页的跳转,即浏览器的前进后退
  • hash模式只能修改#后面的内容,所以只能跳转到与当前url同文档的url
  • hash永远不会提交到server端

History模式

所谓history模式,相较于hash模式的区别只是url中少了一个#,看起来更美观了一点,实现起来较hash模式有点复杂,但也很简单。

首先我们需要了解下面的几个知识点:

  • Window.history

    • 这是一个h5提供的对象,上面有很多与浏览器栈相关的属性和方法,这里我们会使用这上面的一些属性和api
  • Window.history.pushState(state, title, url)

    • pushState方法可以在浏览器栈中push一个新的记录,但当前页面不会刷新
    • 第一个参数为自定义对象,也就是当前页面对应的state对象
    • 第二个参数一般直接使用空字符串,因为会被浏览器忽略
    • 第三个参数,即为新的url路径字符串
  • Window.history.replaceState(state, title, url)

    • 使用方法基本等同于pushState,区别在于pushState在栈里push,而replaceState相当于先pop,再push。
  • popState事件

    • 点击浏览器的前进后退按钮会触发此事件
    • 但是pushStatereplaceState方法的调用不会触发此事件

思路分析

  • 我们可以利用 relaceStatepushState两个api,实现改变url
  • 然后我们需要监听到realceStatepushState两个方法的调用
  • 最后和hash模式一样,进行路由的匹配,但区别在于这次需要使用location.pathname来获取url的地址

代码实现如下:

Html部分

<ul>
    <li><a href="/home">首页</a></li>
    <li><a href="/about">关于</a></li>
</ul>

<!-- 渲染对应的 UI -->
<div id="routerView">

</div>

JS部分

    let routerView = document.getElementById("routerView");
    window.addEventListener('DOMContentLoaded', onLoad) // 所有DOM结构加载完毕
    window.addEventListener("popstate", onPopState) // 浏览器的前进后退也可以生效
    
    function onLoad() {
      onPopState()
      let links = document.querySelectorAll('li a[href]')
      // 拦截 a 标签的默认跳转行为
      links.forEach(a => {
        a.addEventListener('click', (e) => {
          e.preventDefault() // 组织 a 标签的 href 行为
          history.pushState(null, '', a.getAttribute('href')) // 跳转
          onPopState()
        })
      })
    }

    function onPopState() {
      // console.log(location);
      switch(location.pathname) {
        case '/home':
          routerView.innerHTML = 'Home page';
          return
        case '/about':
          routerView.innerHTML = 'About page';
          return
        default:
          return
      }
    }

实现效果如下图:

chrome-capture-2023-6-7.1.gif

总结

  • 通过上面的例子,我们很容易发现,无论是hash模式还是history模式,都是通过监听url的改变,在通过url的改变动态的去更新页面或者组件。但在开发中还是要对二者进行选择。
  • 如果不想要很丑的hash,我们可以用路由的history模式———— 引用自VueRouter官方文档。