前端路由

我们使用 vue 搭建的 SPA(single page web application) 网站,之所以能够做到只加载单个 HTML 页面就可以在后续用户与网页交互时动态更新页面的内容,比如点击某个导航,浏览器的 url 和页面的内容就会相应地改变,看起来就像是页面发生了跳转一样,但事实上页面并没有重载,借助的就是前端路由。

前端路由,简单地说就是 url 与内容的映射,有 2 种实现方式:hash 和 history,分别对应着 Vue Router 中的 Hash 模式和 HTML5 模式,本篇文章就对它们进行个介绍。

URL 的 hash

在一个很长的页面中,想实现点击某个导航让页面滚动到指定位置,我们就可以使用到 url 的 hash:

html 复制代码
<!-- 代码片段 1 -->
<a href="#1">1</a>
<a href="#2">2</a>
<div style="height: 100vh">---</div>
<div id="1">1</div>
<div style="height: 100vh">---</div>
<div id="2">2</div>

如下图所示,点击 <a href="#1">1</a>,页面就会滚动到 <div id="1">1</div>。可以看到,url 后面多出了 "#1" ,页面也没有重载。

利用 url 的 hash 的特性,我们就可以实现个简单的前端路由了:

html 复制代码
<!-- 代码片段 2 -->
<a href="#home">首页</a>
<a href="#profile">个人中心</a>
<div id="router-view">首页内容</div>
<script>
  const routerView = document.getElementById('router-view')
  window.addEventListener('hashchange', () => {
    switch (location.hash) {
      case '#home':
        routerView.innerHTML = '首页内容'
        break
      case '#profile':
        routerView.innerHTML = '个人中心'
        break
      default:
        routerView.innerHTML = '首页内容'
        break
    }
  })
</script>

a 标签就相当于 Vue Router 中的 <router-link>,而 <div id="router-view"> 就相当于是 <router-view>。当 url 中的 hash 值发生改变时,就会触发 hashchange 事件,然后我们可以通过 location.hash 获取到当前的 url 中的 hash 值,接着就可以根据不同的 hash,往 id 为 router-view 的 div 内插入不同的内容了:

hash 模式的优点是兼容性好,但有个不好的地方在于 url 中会出现一个 "#",对于某些对 url 有执念的人来说可能就会觉得很难受。所幸,HTML5 新增的 history 接口可以解决这个问题。

HTML5 的 history

先看看 MDN 的介绍:

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

history 有 5 个方法可以来改变 url 而不刷新页面。下面还是以点击 a 标签改变 <div id="router-view"> 里的内容为例来做介绍:

html 复制代码
<!-- 代码片段 3 -->
<a href="home">首页</a>
<a href="profile">个人中心</a>
<div id="router-view">首页内容</div>

如果直接像上面这样写,那么点击 <a href="home">首页</a>, 页面会跳转到 http://xxx/home,浏览器会去我们起动的服务器的根目录下寻找 home 文件,重新加载资源,我们需要阻止这一默认行为:

javascript 复制代码
// 代码片段 4
const aEls = document.getElementsByTagName('a')
for (const item of aEls) {
item.addEventListener('click', e => {
  e.preventDefault()
})

pushState()

现在点击 a 标签,浏览器就不会去请求资源了,但 url 也不会有变化,我们可以通过 history.pushState() 来让 url 发生改变,其接收 3 个参数,第一个为状态对象 state,我们可以传一个空对象;第二个参数为 title,当前大多数浏览器都忽略此参数,我们直接传 '',第三个参数就是 url,可以由 a 标签的 href 属性获取:

javascript 复制代码
// 代码片段 5
for (const item of aEls) {
  item.addEventListener('click', e => {
    e.preventDefault()
    const href = item.getAttribute('href')
    history.pushState({}, '', href)
  })
}

在 url 发生改变后通过 location.pathname 获取到当前的路径,再去改变 <div id="router-view">的内容:

javascript 复制代码
// 代码片段 6,接在代码片段 5 history.pushState({}, '', href) 之后
switch (location.pathname) {
  case '/home':
    routerView.innerHTML = '首页内容'
    break
  case '/profile':
    routerView.innerHTML = '个人中心'
    break
  default:
    routerView.innerHTML = '首页内容'
    break
}

效果如下:

可以看到,现在 url 中就没有 "#" 号了。但是请注意,history 模式下,我们的项目需要通过服务器运行,如果是直接通过文件在浏览器打开,点击 a 标签时是会报错的:

而 hash 模式不会有这问题。

popstate 事件

再仔细看上面的动图,可以发现,当我们点击浏览器的后退按钮(左箭头)或前进按钮(右箭头)时,url 虽然变化了,但是 <div id="router-view"> 内的内容没有变化,如果想正确地显示,我们可以监听 popstate 事件:

javascript 复制代码
// 代码片段 7
window.addEventListener('popstate', () => {
  switch (location.pathname) {
    case '/home':
      routerView.innerHTML = '首页内容'
      break
    // ... 省略,同代码片段 6 一样
  }
})

现在点击前进后退,内容就会对应地改变了:

replaceState()

但是,如果在代码片段 5 中,我们使用的不是 pushState() 而是 replaceState(),则不会在历史堆添加 一个状态,而是修改当前历史记录实体,这就导致我们点击 a 标签后,后退按钮仍然不可用:

其余方法

history 还有 back()forward()go() 方法,比较简单,见名知意,可参见 MDN 文档,就不多做介绍了。

相关推荐
excel11 分钟前
webpack 核心编译器 十四 节
前端
excel18 分钟前
webpack 核心编译器 十三 节
前端
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰11 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪11 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪11 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy12 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github