路由最早是用来描述服务器上的资源路径。
-
单页应用:整个项目就算有很多个页面,也只有一个html文件,每个页面被处理成组件都移交到这一个html文件里面来展示。其中路由通常是通过前端JavaScript来实现的。当点击链接或输入URL时,前端路由器会根据路径匹配相应的组件。
-
多页应用:有很多html,每个页面对应一个html文件。其中路由通常是由服务器端来控制的。当请求一个URL时,服务器会根据请求的路径返回相应的HTML页面。
前端路由
前端路由就是通过 url 路径来匹配想要展示的内容也就是代码块的这么一套机制。在单页应用中,点击跳页面浏览器是不会重新刷新的。
要实现前端路由就需要解决:
- 修改url后,页面要更新
- 浏览器不刷新
- 如何监听 url 变更
我们要改变 url 地址栏就需要用到<a href="/home">首页</a>
的标签,但是这个点击就会刷新浏览器,在整个html中,但是只有a标签的href属性可以修改浏览器的地址栏。但是如果在路径前面加#
号,如<a href="#/home">首页</a>
,这样浏览器就不会刷新了,这是浏览器本身就有的机制,在浏览器眼里,只要url地址栏出现#
,其后面所有出现的内容都会被认定为一串hash值,而hash值的变更不会引起浏览器的刷新的。
hash 路由
#/home
在浏览器的url中出现 #
号,#
后面的内容会被看作是一个 hash 值,hash 值得变化不会引起页面的重新加载。然后就是监听url的hash值变化,让对应代码块出现。创建一个数组,里面每项是一个对象,分别有
<body>
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="app"></div>
<script>
const routes = [
{
path: "/home",
component: () => {
return "首页页面"
}
},
{
path: "/about",
component: () => {
return "关于页面"
}
}
]
const app = document.getElementById("app");
window.addEventListener("hashchange", () => {
const localhash = window.location.hash;
console.log(localhash); // #home
routerView(localhash)
})
// 地址栏如果刷新重新调用routerView
window.addEventListener("DOMContentLoaded", () => {
routerView(location.hash)
})
function routerView(localhash) {
// 去 routes 数组中查找 localhash 值在哪一项中
// 找到了就返回这一项中的 component 执行结果放进 app 中
const index = routes.findIndex((item) => {
return '#' + item.path === localhash
})
const content = routes[index].component()
app.innerHTML = content
}
</script>
</body>
</html>
主要思路就是在a标签中使用#
号,使得浏览器不刷新,然后定义一个路由数组,里面每一项为一个对象,对象中的属性为path
路径,component
要展示的代码块,然后监听hashchange
hash值改变事件,使用window.location.hash
获取当前hash值,然后遍历数组从里面找到对应的代码块将其显示到页面,最后需注意到浏览器刷新时,hash不变,但是页面会重新刷新,还要监听DOMContentLoaded
页面加载完毕事件。hash后面的为无效地址。
history 路由
a标签默认点击会跳转,就需要首先禁止掉a标签的默认跳转行为e.preventDefault()
,然后使用 history.pushState(null,'',this.getAttribute('href'))
,来改变 url 路径,history是浏览器环境下独有的一个对象(栈结构),用来管理浏览器的前进和后退。然后使用loacltion.pathname
来读取本地url地址路径。然后判断拿取代码块去展示。最后使用监听popstate
浏览器前进后退按钮,
<body>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于</a></li>
<div id="app"></div>
<script>
const routes = [
{
path: '/home',
component: () => {
return "<h1>首页页面</h1>"
}
},
{
path: '/about',
component: () => {
return "<h1>about页面</h1>"
}
},
]
// 监听浏览器前进后退按钮
window.addEventListener('popstate', function () {
routerView()
})
let linkList = document.querySelectorAll('a')
linkList.forEach(link => {
link.addEventListener('click', function (e) {
// 让 a 标签不跳转
e.preventDefault() // 阻止默认行为
// 让 url 发生变化
// history.pushState(null, '', this.getAttribute('href'))
history.pushState(null, '', this.getAttribute('href')) // 可以修改url,不会带来页面刷新
routerView()
})
})
const app = document.getElementById('app')
function routerView() {
const index = routes.findIndex((item) => {
return item.path === location.pathname
})
app.innerHTML = routes[index].component()
}
</script>
</body>
</html>
小结
- hash 路由:
#/home
在浏览器的url中出现 # 号,# 后面的内容会被看作是一个 hash 值,hash 值得变化不会引起页面的重新加载,从而直接监听 hashchange 来判断要展示对应的组件 - history 路由:首先阻止 a 标签的默认跳转行为,然后使用 history.pushState() 来改变url路径,它是不会引起页面的重新加载的,在 url 变更后,读取本地的 pathname 来判断要展示的对应的组件。考虑到浏览器的前进后退按钮的存在,需要监听 popstate 事件(前进后退事件),来判断要展示的对应组件。