前端路由

我们使用 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 文档,就不多做介绍了。

相关推荐
不在逃避q6 分钟前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁20 分钟前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One98525 分钟前
高进度算法模板速记(待完善)
java·前端·算法
陈随易30 分钟前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师2 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户059540174462 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈2 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
kyriewen3 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒3 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端
梅孔立4 小时前
两种免费的翻译API调用方式详解 - Edge官方API与个人缓存服务
前端·缓存·edge