目录
一、核心原理
1.什么是前端路由?
在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。
2.如何实现前端路由?
要实现前端路由,需要解决两个核心:
-
如何改变 URL 却不引起页面刷新?
-
如何检测 URL 变化了?
下面分别使用 hash 和 history 两种实现方式回答上面的两个核心问题。
hash 实现(前置知识:Location - Web API 接口参考 | MDN)
hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新
通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:
- 通过浏览器前进后退改变 URL
- 通过
<a>
标签改变 URL - 通过window.location改变URL
history 实现(前置知识:
window.history详解一: 基础篇_chexijieyugeng8257的博客-优快云博客
History.pushState() - Web API 接口参考 | MDN)
history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:
- 通过浏览器前进后退改变 URL 时会触发 popstate 事件
- 通过pushState/replaceState或
<a>
标签改变 URL 不会触发 popstate 事件。 - 好在我们可以拦截 pushState/replaceState的调用和
<a>
标签的点击事件来检测 URL 变化 - 通过js 调用history的back,go,forward方法课触发该事件
所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
二、原生js实现前端路由
1.基于 hash 实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
</ul>
<div id="routerView"></div>
</body>
<script>
let routerView = document.getElementById("routerView")
const handleHashchange = ()=>{
routerView.innerHTML = location.hash
}
window.addEventListener('hashchange', handleHashchange)
window.addEventListener('DOMContentLoaded', handleHashchange)
</script>
</html>
解释下上面代码,其实很简单:
- 我们通过a标签的href属性来改变URL的hash值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入window.location赋值来改变hash)
- 我们监听hashchange事件。一旦事件触发,就改变routerView的内容,若是在vue中,这改变的应当是router-view这个组件的内容
- 为何又监听了load事件?这时应为页面第一次加载完不会触发 hashchange,因而用load事件来监听hash值,再将视图渲染成对应的内容。
2.基于 history 实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
</ul>
<div id="routerView"></div>
</body>
<script>
let routerView = document.getElementById("routerView")
let linkList = document.querySelectorAll('a')
linkList.forEach(el=>{
el.addEventListener('click', event => {
event.prevent()
history.pushState(null, '', el.getAttribute('href'))
})
})
const handleHistorychange = ()=>{
routerView.innerHTML = location.pathname
}
window.addEventListener('hashchange', handleHistorychange)
window.addEventListener('DOMContentLoaded', handleHistorychange)
</script>
</html>
解释下上面代码,其实也差不多:
- 我们通过a标签的href属性来改变URL的path值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入history.go,back,forward赋值来触发popState事件)。这里需要注意的就是,当改变path值时,默认会触发页面的跳转,所以需要拦截
<a>
标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。 - 我们监听popState事件。一旦事件触发,就改变routerView的内容。
- load事件则是一样的
有个问题:hash模式,也可以用history.go,back,forward
来触发hashchange事件吗?
A:也是可以的。因为不管什么模式,浏览器为保存记录都会有一个栈。