什么是前端路由
传统的多页面应用程序,页面跳转会刷新并请求服务器, 服务器控制路由根据url返回相应的响应或者资源(返回一个完整的html页面包含css,js图片等).浪费带宽且用户体验不好;
面对日益增长的网页需求,网页开始走向模块化、组件化的道路,vue,react,angular等前端框架的出现让单页面应用程序变成了大趋势.
单页面应用程序只有第一次会请求服务器,加载完整的页面,页面跳转不会刷新页面;之后的请求仅仅获取必要的数据,减少了请求体积,加快页面响应速度,降低了对服务器的压力,有更好的用户体验,运行更加流畅
前端路由就是单页面网站,根据浏览器地址路径的不同,来匹配相对应的页面组件
原理
hash模式
hash模式最显著的特点就是浏览器地址栏有#,#后面的值就是hash值
浏览器有一个原生事件叫做hashchange,通过hashchange事件可以监听浏览器#后面的hash值的变化,从而匹配相对应的组件显示到页面上
hash模式简易路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
<ul>
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
</ul>
<!--渲染对应组件的地方-->
<div id="route-view"></div>
</div>
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', Load) // 第一次加载的时候,不会执行hashchange监听事件,所以可以在dom加载完成时默认执行一次hashchange
window.addEventListener('hashchange', HashChange)
var routeView = null // 展示页面组件的节点
function Load() {
routeView = document.getElementById('route-view')
HashChange()
}
function HashChange() {
// 每次触发 hashchange 事件,通过 location.hash 拿到当前浏览器地址的 hash 值,根据不同的路径展示不同的内容
switch(location.hash) {
case '#/page1':
routeView.innerHTML = 'page1'
return
case '#/page2':
routeView.innerHTML = 'page2'
return
default:
routeView.innerHTML = 'page1'
return
}
}
</script>
</body>
</html>
history模式
history模式对比hash模式最大的特点是浏览器地址没有#,且history模式依赖的是popstate事件
a标签的点击事件,history.pushsState,history.replaceState不会触发popstate,所以需要做一些特殊处理
tip: 如果使用history模式,需要后端或者nginx配合配置,否则刷新后页面会404(原因是history模式没有# 刷新后浏览器会将整个页面地址当成url去请求服务端,而url中有一部分其实是前端的路由,服务端根据url肯定是找不到资源的,则返回404了)
history模式简易路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
<ul>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div id="route-view"></div>
</div>
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', Load) // 在dom加载完成时默认执行一次popstate
window.addEventListener('popstate', PopChange)
var routeView = null
function Load() {
routeView = document.getElementById('route-view')
PopChange() // 默认执行一次 popstate 的回调函数,匹配一次页面组件
var aList = document.querySelectorAll('a[href]') // 获取所有带 href 属性的 a 标签节点
aList.forEach(aNode => aNode.addEventListener('click', function(e) { // 遍历 a 标签节点数组,阻止默认事件,添加点击事件回调函数
e.preventDefault() //阻止a标签的默认事件
var href = aNode.getAttribute('href')
history.pushState(null, '', href) // 通过手动修改浏览器的地址栏
PopChange() // popstate 是监听不到地址栏的变化,所以此处需要手动执行回调函数 PopChange
}))
}
function PopChange() { //popstate的事件监听处理函数
console.log('location', location)
switch(location.pathname) {
case '/page1':
routeView.innerHTML = 'page1'
return
case '/page2':
routeView.innerHTML = 'page2'
return
default:
routeView.innerHTML = 'page1'
return
}
}
</script>
</body>
</html>