前端路由

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

相关推荐
IT_陈寒13 小时前
Python 3.12性能优化实战:5个让你的代码提速30%的新特性
前端·人工智能·后端
赛博切图仔13 小时前
「从零到一」我用 Node BFF 手撸一个 Vue3 SSR 项目(附源码)
前端·javascript·vue.js
爱写程序的小高13 小时前
npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree
前端·npm·node.js
loonggg13 小时前
竖屏,其实是程序员的一个集体误解
前端·后端·程序员
程序员爱钓鱼13 小时前
Node.js 编程实战:测试与调试 - 单元测试与集成测试
前端·后端·node.js
码界奇点13 小时前
基于Vue.js与Element UI的后台管理系统设计与实现
前端·vue.js·ui·毕业设计·源代码管理
时光少年14 小时前
Android KeyEvent传递与焦点拦截
前端
踢球的打工仔14 小时前
typescript-引用和const常量
前端·javascript·typescript
OEC小胖胖14 小时前
03|从 `ensureRootIsScheduled` 到 `commitRoot`:React 工作循环(WorkLoop)全景
前端·react.js·前端框架
时光少年14 小时前
ExoPlayer MediaCodec视频解码Buffer模式GPU渲染加速
前端