目录
路由
路由一词最早来自服务器,和前端没有关系。当你想要从服务器中读取某个盘的文件,这个文件的路径就是路由。也就是说路由是服务器端用来描述路径的,或者是说url和文件的映射关系
后来因为前端的spa单页应用,前端也借鉴了路由这个概念。浏览器的url变了需要映射到页面的某个组件,url变了需要展示某个组件。/home和home.vue,/about和about.vue就是一一映射的关系。前端借鉴路由的称呼来描述url和组件的映射关系。这个时候你就想起来router中index.js文件中,一个path对应一个component,也就是一个路径对应一个组件
实现路由需要解决的问题
- 如何修改url还不引起页面的刷新
- 如何知道url变化了
若是能解决这两个问题就可以实现前端路由了。
哈希hash
哈希是一种值,按照某种规则生成的一串值,用来代表一个唯一的文件,文件名后加一个哈希值,可以看到文件是否被修改过。
在浏览器中也有hash这个概念,url中接一个#
,#
后的值就是哈希值,按道理url变了,页面一定会刷新,但是哈希是个特例,放个哈希值就是不会刷新页面,这样,我们就解决了第一个问题,修改url不引起页面的刷新
在浏览器url后加个哈希值,哈希值的变更不会引起浏览器页面的刷新
下面利用哈希模式实现路由
哈希手搓一个路由
我们新建一个hash.html文件,放两个a标签,但是a标签有个机制,就是点击必定会引起页面的刷新。但浏览器的机制是哈希值的变更不会引起页面刷新,所以地址放哈希值可以解决这一问题
xml复制代码<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于</a></li>
</ul>
<div id="routeview">
<!-- 放一个代码片段 点击首页首页代码片段生效,反之关于生效-->
</div>
现在模拟一个场景,如果点击首页,routeview
容器展示首页的内容,点击关于routeview
容器展示关于页面的内容,如果能够实现,路由就可以实现了
自行封装一个路由,先写一个路由的映射关系
xml复制代码<script>
const routes = [
{
path: '#/home',
component: '首页内容'
},
{
path: '#/about',
component: '关于页面内容'
}
]
</script>
点击首页,展示首页内容
,点击关于,展示关于首页内容
。
接下来的事情就是点击url,我们需要知道url的变化。我们不可能给按钮添加一个点击事件,如果项目大起来,按钮很多,每次点击一个按钮都判断一次url的变化,会非常的不优雅。
js自带一个hashchange
事件,它可以自动监听hash值的变更。当我们点击首页的时候,下面的代码都会执行一次,因为hash值变了
javascript复制代码window.addeventlistener('hashchange', () => {
console.log('changed')
})
这样,第二个问题我们已经解决了。非常之简单!
我们现在把监听器的回调函数写出来,拿到当前的哈希值,去对应component。
javascript复制代码window.addeventlistener('hashchange', onhashchange)
function onhashchange() {
console.log(location)
}
location代表window窗口的url,我们运行打印这个location看看
看到没有,里面刚好有个hash值,我们可以把这个拿出来去对应component!
这个时候直接去数组中匹配就可以,foreach遍历
scss复制代码function onhashchange() {
console.log(location)
routes.foreach((item, index) => {
if(item.path === location.hash) {
routeview.innerhtml = item.component
}
})
}
当然,记得拿到routeview的dom结构
这样写会有个问题,就是页面刚加载完毕的时候不会去加载当前的路由,想要hashchange在页面初次加载的时候触发一次,那就给一个监听dom结构的事件,dom一出来就会执行,也就是说页面加载完毕就调用一次hashchange
javascript
复制代码window.addeventlistener('domcontentloaded', onhashchange)
好了,最终的hash.html如下
xml复制代码<body>
<!-- 模拟单页页面应用 -->
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于</a></li>
<!-- 判断url的变化,绑定点击事件不好,页面过多就很累赘,有个hashchange的官方方法 -->
</ul>
<div id="routeview">
<!-- 放一个代码片段 点击首页首页代码片段生效,反之关于生效-->
</div>
<script>
const routes = [
{
path: '#/home',
component: '首 容'
},
{
path: '#/about',
component: '关于页面内容'
}
]
const routeview = document.getelementbyid('routeview')
window.addeventlistener('domcontentloaded', onhashchange) // 与vue的声明周期一个道理,dom一加载完毕就触发
window.addeventlistener('hashchange', onhashchange)
function onhashchange() {
console.log(location) // url详情,里面就有个hash值 liveserver可以帮你把html跑成服务器
routes.foreach((item, index) => {
if(item.path === location.hash) {
routeview.innerhtml = item.component
}
})
}
</script>
</body>
其实这就是vue-router中两种模式之一哈希模式,哈希模式就是这样是实现的。
修改地址栏
- a标签
- 浏览器前进后退
- window.location
以上方式导致url变更都会触发hashchange事件。
那问题来了,history模式没有哈希是如何实现的呢?没有哈希值a标签一定会引起页面的刷新,如何解决?我们继续看下去
history手搓一个路由
我们先看下history在mdn中的介绍
history - web api 接口参考 | mdn (mozilla.org)
我们重点看一个history自带的方法pushstate
它可以修改url且不引起页面的刷新
浏览器中有个会话历史栈
,它可以维护你的访问路径,有了这个你返回就可以按照栈的顺序进行前进回退。
pushstate
提到了popstate
,他是靠popstate
监听url的改变的,并且仅当浏览器前进后退时生效
既然如此,我们现在开始手搓
同样是上面的情景,两个a标签,一个首页,一个关于页面。
xml复制代码<ul>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
<div id="routeview">
给个url和组件的对应关系数组,已经不用哈希了
xml复制代码<script>
const routes = [
{
path: '/home',
component: '首页内容'
},
{
path: '/about',
component: '<h1>关于页面内容</h1>'
}
]
</script>
a标签有个默认的页面跳转效果,既然现在不用哈希,我们就需要自己把a标签的页面跳转刷新效果干掉
先拿到所有的a标签
dart
复制代码const links = document.queryselectorall('li a')
再去禁用掉默认的跳转行为,它跳转一定会带来刷新,要干掉它
javascript复制代码links.foreach(a => {
a.addeventlistener('click', (e) => {
console.log(e)
e.preventdefault() // 阻止a的跳转行为
})
})
我们可以打印看看这个事件参数,顺着原型链找到event对象,里面有个preventdefault,这个就是禁用a标签默认的跳转行为
接下来添加一个可以修改url又不引起页面刷新的方法,就是pushstate,具体用法查看mdn
他有三个参数,第一个参数是javascript对象,一般不需要,给个null就好,第二个参数由于历史原因,写个空字符,不写会有问题,第三个参数是新的url
新的url肯定是点了什么放什么url,所以我需要读取到a标签的href值
less
复制代码a.getattribute('href')
以上方法是核心,这里已经实现了哈希一样的效果,并且没有难看的#
,pushstate
的核心原理就是它会往浏览器的历史栈中塞一个值进去,让浏览器显示新的值,并且不引起页面的刷新
接下来就是要去感知到url的变化,去一一对应组件的展示
我们写一个函数,来实现这个功能。还是一样的,先拿到当前的浏览器地址
复制代码location.pathname
然后再进行遍历,去添加组件
ini复制代码routes.foreach((item) => {
if(item.path === location.pathname) {
routeview.innerhtml = item.component
}
})
同样的,我们需要在页面初次加载的时候调用函数
但是浏览器的前进后退没有触发上面的遍历函数,popstate刚好填补这个空缺
javascript
复制代码window.addeventlistener('popstate', onpopstate)
好了,最终的history.html如下
xml复制代码<body>
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
<div id="routeview">
</div>
<script>
const routes = [
{
path: '/home',
component: '首页内容'
},
{
path: '/about',
component: '<h1>关于页面内容</h1>'
}
]
const routeview = document.getelementbyid('routeview')
window.addeventlistener('domcontentloaded', onload)
window.addeventlistener('popstate', onpopstate)
function onload() {
const links = document.queryselectorall('li a') // 获取所有的li下的a标签
// console.log(links)
links.foreach((a) => {
// 禁用a标签的默认跳转行为
a.addeventlistener('click', (e) => {
console.log(e)
e.preventdefault() // 阻止a的跳转行为
history.pushstate(null, '', a.getattribute('href')) // 核心方法 a.getattribute('href')获取a标签下的href属性
// 映射对应的dom
onpopstate()
})
})
}
function onpopstate() {
console.log(location.pathname)
routes.foreach((item) => {
if(item.path === location.pathname) {
routeview.innerhtml = item.component
}
})
}
</script>
</body>
效果如下
最后
本期文章主要介绍了路由这个概念,以及重点讲前端实现路由的两种模式,哈希刚好就是浏览器承认他,接了哈希值改变url不会引起页面的刷新,然后通过location.hash
得知哈希值;history就是有个方法,可以改变url不引起页面刷新的pushstate
,通过location.pathname
得知url,这个模式下前进回退需要通过popstate
事件来触发。
发表评论