本文共 3275 字,大约阅读时间需要 10 分钟。
在 Web SPA 中,前端路由描述的 URL 与 UI 之间的单向映射关系,即 URL 变化引起 UI 页面的更新(无需刷新页面)。
上面我们提到,在前端路由中,当 URL 发生变化时,我们需要在不刷新页面的情况下,触发 UI 页面的更新。因此,在实现前端路由时,我们需要解决以下两个核心的问题。
我们可以从 Hash 和 History 两种实现方式回答上述两个问题。
hashchange
事件监听 URL 的变化,以下场景会触发 hashchange
事件:通过浏览器前进后退改变 URL 、通过标签改变 URL 、通过 window.location
改变 URL 。Hash 是 URL 中 # 及后面的部分,改变 URL 中的 Hash 部分不会引起页面刷新。popstate
事件监听 URL 的变化。我们可通过调用 pushState
和 replaceState
两种方法,改变 URL 而不引起页面刷新。值得注意的是,通过浏览器前进后退改变 URL 时会触发 popstate
事件,而通过 pushState
、replaceState
或标签改变 URL 并不会触发 popstate
事件,因此我们需要手动拦截。代码地址:
为便于测试,我们使用了 作为页面服务器,相关命令如下。
# 安装依赖。yarn# 对 Vanilla Hash 进行演示。yarn vanilla.hash# 对 Vanilla History 进行演示。yarn vanilla.history复制代码
现在,我们使用原生 HTML/JS 实现 Hash 和 History 两种模式的前端路由,不依赖任何框架。
Hash
页面 vanilla.hash.html
的具体代码如下。
复制代码Hash Route - Vanilla
在 vanilla.hash.js
中,我们通过对 hashchange
事件进行监听,从而检测 URL 是否变化。当 URL 变化时,我们调用 onHashChange
函数,通过修改元素的 innerHTML
属性,在页面无刷新的情况下,实现页面视图的更新。
// 维护 UI 页面。let routerView = null;// 路由变化时,根据路由渲染对应 UI 页面。function onHashChange() { switch (window.location.hash) { case '': case '#/home': routerView.innerHTML = 'Home'; return; case '#/about': routerView.innerHTML = 'About'; break; default: }}// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件。window.addEventListener('DOMContentLoaded', () => { routerView = document.querySelector('#routeView'); onHashChange();});// 监听路由变化。window.addEventListener('hashchange', onHashChange)复制代码
History
页面 vanilla.history.html
的具体代码如下。
复制代码History Route - Vanilla
在 vanilla.history.js
中,我们通过对 popstate
事件进行监听,从而检测 URL 是否变化。当 URL 变化时,我们调用 onPopState
函数,通过修改元素的 innerHTML
属性,在页面无刷新的情况下,实现页面视图的更新。
由于通过 pushState
、replaceState
和标签改变 URL 时,并不会触发 popstate
事件,我们需要手动拦截。
// 维护 UI 页面。let routerView = null;// 路由变化时,根据路由渲染对应 UI 页面。function onPopState() { switch (window.location.pathname) { case '/': case '/home': routerView.innerHTML = 'Home'; return; case '/about': routerView.innerHTML = 'About'; break; default: }}// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件。window.addEventListener('DOMContentLoaded', () => { routerView = document.querySelector('#routeView'); // 刷新页面。 onPopState(); // 拦截 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。 const links = document.querySelectorAll('a[href]'); links.forEach(el => el.addEventListener('click', function handler(e) { e.preventDefault(); // 手动拦截。 window.history.pushState(null, '', el.getAttribute('href')); onPopState(); }), );});// 监听路由变化。window.addEventListener('popstate', onPopState);复制代码
在使用 History 模式时,当我们刷新页面,会出现 404 页面。为解决这个问题,我们需要 rewrite 请求。
在 Nginx 中,我们需要添加以下设置:对于所有的请求,我们都响应相同的页面,让页面来接管路由。
location / { try_files $uri $uri/ /vanilla.history.html;}复制代码
在 serve
中,我们只需定义 rewrites
数组即可。
{ "rewrites": [ { "source": "**", "destination": "vanilla.history.html" } ]}复制代码