前言
大家好,这里是我不是程序猿kk。今天想和大家聊一聊前端路由的实现原理,注意: 是一个大的概念,前端,而不仅仅是vue/react
本文将从是什么到为什么最后落到怎么做进行详细,以大白话的方式介绍。
前端路由
是什么
举个例子,当我们用vue创建一个项目,打开后在you did it!页面会看见两个按钮,一个about,一个home。当点击某个按钮,就会进行路由的跳转。
那么什么是前端路由,路由存在的意义是什么?
像vue这种SPA
,当我们修改了url地址栏后,接下来要做的就是想办法把某个代码片段(组件)挂到这一份html的某个位置。所以路由的意义其实就是当用户修改了url后,我们给用户加载对应的组件。
曾经我们开发多页应用时,从某个html跳转到其他html,用a标签的href属性,这不属于路由的概念,这是路径的跳转。
现在单页应用的时代下,如何在一份html中打开另一份html?事实上不需要,我们只需要这一份html,不断在这一份html中替换其他的代码片段,这个功能就要依靠路由了。
再回到刚刚的例子,点击about按钮后,就会挂载about页面,但是在这个过程中我们可以看见,页面是不会刷新的
,细心的同学会发现,如果我们去访问百度的首页,点击图片按钮,这个时候会刷新,跳转到另一个页面,这属于多页应用。
所以前端路由的概念
其实就是url和页面的映射关系。前端本无路由,最早路由的概念是用来称呼服务器上面的资源路径,比如服务器上的一个图片在某个文件的某个文件夹的某个文件上,这个完整路径称为路由。后来路由的概念才被广泛借鉴到各个领域中使用,包括前端、后端、node等等。
并且要满足:
- 当url修改了,页面要更新
- 浏览器不能刷新(刷新了就不叫路由了,路由这个概念出现在SPA中,如果不是SPA也不需要路由这个概念了)
思考: 什么行为可以更改url?这一点很简单,用户输入、js控制...,但是,我们如何知道url更改了?
hash(浏览器天生支持)
url地址中,也能放hash值,www.baidu.com#abc 井号xxx,后面接了井号包括井号后面的东西,都会被浏览器识别成hash值,那么我们可以去尝试一下,当我们在后面接abcdefg,然后回车,页面会刷新吗?——不会
因此,这就是前端路由实现的第一套方案(有什么办法修改url还不引起页面的刷新),这也是前端路由中大名鼎鼎的一套方案,例如vue中创建路由,createWebHashHistory
如何实现? 我们来创建一份hash.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于</a></li>
</ul>
</body>
</html>
点击按钮,url更改了,但是页面不刷新,接下来要干的就是页面的映射,在vue中是这样的
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView />
也就是说需要一个路由入口,将需要展示的结构展示到这个地方,那我们也来试一试
hash.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于</a></li>
</ul>
<!-- 展示页面-->
<div id="routerView"></div>
</body>
<script>
let routerView = document.getElementById('routerView')
const routes = [
{
path: '/home',
component: () => {
return '<h2>首页</h2>'
}
},
{
path: '/about',
component: () => {
return '<h2>关于页</h2>'
}
}
]
window.addEventListener('DOMContentLoaded', ()=>{
renderView(location.hash);
})
// 监听页面上的hash值的变更
window.addEventListener('hashchange', () => {
console.log('hashChange')
// 当前hash值
console.log(location.hash)
renderView(location.hash)
})
function renderView(locationHash) {
const index = routes.findIndex(item => {
return '#' + item.path === locationHash
})
console.log(index)
routerView.innerHTML = routes[index].component()
}
</script>
</html>
效果
history
相比于hash,history就要复杂一些了,毕竟浏览器天生就能识别hash这种模式,hash又天生不会让页面刷新,天生有hashchange事件给你用,还有缓存栈,当点击回退,页面会回退。
html提供的history对象拥有pushState和replaceState两个方法,他们可以用来修改url,不会引起页面的刷新
当a标签被点击时,通过阻止a的默认跳转行为,人为的使用pushState修改url,再通过监听事件popstate来控制浏览器的前进后退 history.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
<div id="routerView">
</div>
</body>
<script>
let routerView = document.getElementById('routerView')
window.addEventListener('DOMContentLoaded', ()=>{
onLoad()
})
window.addEventListener('popstate', ()=>{ // 监听浏览器的回退按钮
renderView(location.pathname)
})
const routes = [
{
path: '/home',
component: () => {
return '<h2>首页</h2>'
}
},
{
path: '/about',
component: () => {
return '<h2>关于页</h2>'
}
}
]
function renderView(pathName) {
const index = routes.findIndex(item => {
return item.path === pathName
})
console.log(index)
routerView.innerHTML = routes[index].component()
}
function onLoad(){
let linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => {
el.addEventListener('click', (e)=>{
e.preventDefault() // 阻止默认行为 阻止页面跳转
history.pushState(null,'',el.getAttribute('href')) // 不进入浏览器的缓存栈
// renderView(el.getAttribute('href'))
renderView(location.pathname)
})
})
}
</script>
</html>
结语
本文着重介绍了前端路由是什么,有哪些模式,模式的原理是什么,并以简化源码的形式让大家明白,底层是通过哪些手段实现的,分别存在什么问题。
如有疑问,欢迎私信讨论。创作不易,如有帮助,烦请一键三连~