前端路由的奇妙冒险:从稚嫩走向成熟的全过程(番外篇)

184 阅读4分钟

导航

History API

HTML5标准的出现,给history API带来了华丽变身。 history 很早就有,在浏览器历史记录里游走 ,而HTML5赋予它修改URL的超能力!

History 初理解

我们先创建三个html文件 index.html、about.html、concat.html

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
</head>
<body>
    <h1>欢迎来到主页</h1>
    <a href="about.html">关于我们</a>
    
</body>
</html>
  • about.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>关于我们</title>
</head>
<body>
    <h1>关于我们</h1>
    <a href="concat.html">联系</a>
    <button onclick="">返回主页</button>
</body>
</html>
  • concat.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>联系</title>
    
</head>
<body>
    <h1>联系</h1>
    <button onclick="">返回主页</button>
    <button onclick="">后退一页关于我们</button>
    <button onclick="">后退一页</button>
</body>
</html>

在about.html编写函数NavigateHistory

<h1>关于我们</h1>
<a href="concat.html">联系</a>
<button onclick="NavigateHistory(-1)">返回主页</button>
<script>
    function NavigateHistory(steps) {
        history.go(steps);
    }
</script>

history.go API 根据历史跳转到对应的页面。这里我们给了个-1,便是跳转到上一个页面,而上一个页面是首页。(不考虑直接在地址栏输入/about.html,而是直接从index开始)

效果:

路由11.gif

concat.html里也是类似的

<h1>联系</h1>
<button onclick="NavigateHistory(-2)">返回主页</button>
<button onclick="NavigateHistory(-1)">后退一页关于我们</button>
<button onclick="history.back()">后退一页</button>
<script>
    function NavigateHistory(steps) {
        history.go(steps);
    }
</script>

路由12.gif

这样我们对History便有了一个初始的了解,我们再来深入探讨一下,它究竟是怎么做到的。

✨ 革命性的API三兄弟

  1. history.pushState() - 新增历史记录(不刷新页面)
  2. history.replaceState() - 替换当前历史记录
  3. popstate 事件 - 监听历史记录变化 这组API就像成熟的绅士,让URL变得优雅:http://yourdomain.com/about 终于摆脱了 # 的束缚!

SPA 路由模拟

我们随便新建一个html文件。

在body里编写页面。

<h2>SPA 路由模拟</h2>
<button onclick="navigate('/home')">首页</button>
<button onclick="navigate('/about')">关于</button>
<button onclick="navigate('/concat')">联系</button>
<button onclick="navigate('/login')">登录</button>
<button onclick="">支付</button>
<div id="view">当前视图</div>

接着在script中编写navigate函数和render函数

<script>
function navigate(path) {
    history.pushState({path},'',path)
    render(path);
}

function render(path) {
    document.getElementById('view').textContent= `当前视图: ${path}`
}
</script>

navigate负责新增历史记录并且调用render渲染view

history.pushState({path},'',path) 可以把login,about等显示在地址栏,并且存入历史记录

让我们看看效果。

路由13.gif

之后编写一个事件监听函数,用于实现历史回退和前进

// 监听浏览器前进/后退事件
window.addEventListener('popstate', (event) => {
    console.log('pop state fired: ', event.state);
    render(event.state.path || location.pathname);
})
  • popstate 事件:当浏览器的活动历史记录条目更改时触发(通常由用户点击前进/后退按钮或通过 history.back() / history.forward() 调用触发)

我们看看效果。

路由14.gif

我们看到回退的历史都记录下来了。但是我们也发现,最后会报一个错,这是为什么呢?

报错原因是 event.state 为 null 时尝试访问 path 属性。为什么 event.state 会是 null ?

初始页面加载 :首次打开页面或刷新时,浏览器历史记录的初始状态没有关联的 state 对象

如何解决这个报错呢?我们可以用可选链操作符做一个判断。

// 监听浏览器前进/后退事件
window.addEventListener('popstate', (event) => {
    console.log('pop state fired: ', event.state);
    render(event.state?.path || location.pathname);
})

再来看看效果。

路由15.gif

最后我们想要一种效果,当登录成功后进入支付页面后,我们返回上一个页面,不应该回到登录页面了,而应该跳过登录页面,去登录页面的上一个页面。

因为我们已经登录成功了,不必重复登录了。 history.replaceState 我们该怎么做呢?这就需要用history.replaceState

history.replaceState

我们编写一个replace函数,同时让pay的onclick执行该函数。

<button onclick="replace('/pay')">支付</button>
 function replace(path) {
    history.replaceState({path}, '', path);
    render(path);
}

路由16.gif

history.replaceState 修改历史记录 :替换当前历史记录条目,而不是添加新的条目

所以它把本是login的path修改为了pay。所以返回上一个页面是concat。

与 pushState 的区别

  • pushState :添加新的历史记录条目,浏览器历史记录长度增加
  • replaceState :替换当前历史记录条目,浏览器历史记录长度不变

到这我们就基本了解History API。

总结

从 # 号的丑小鸭时期,到history API的优雅转身,再到React Router的工业化生产,前端路由的进化史就是一部追求更好用户体验的奋斗史。

当你在 About.jsx 中写下简单的About组件,却能通过路由系统无缝展现在用户面前时,是否想过这背后凝聚了多少开发者的智慧?

前端路由的冒险还在继续,而我们——永远是这场冒险中最勇敢的探险家!

t0102a3073dc2e882db.jpg