这篇文章主要介绍了路由的 hash 模式和 history 模式的实现。vue 中的 router 可根据不同 url 展示对应组件。hash 模式中,# 后的内容为 hash 值,变化不引起页面重载,监听 hashchange 事件加载对应组件。history 模式需阻止 a 标签默认行为,用 () 改 url 路径,监听 popstate 事件加载组件。
关联问题: hash模式有何局限 history路由咋优化 路由实现有何难点
1. 前端router是什么
在传统的html编程中,如果想要实现页面能够展示不同页面那么我们就得写很多的html页面来根据url进行展示,但是在vue项目中是一个单页应用的状态,在vue项目中只有一个index.html,所有的页面或者叫组件都是在这个html中展示,而vue中的router就可以实现根据不同url展示对应的组件。
router: 通过url路径来匹配对应的代码块的这么一套机制
router特点:
- 修改 url 后,页面要更新
- 浏览器不刷新
- 如何监听 url 变更
2. hash路由实现
1const router = createRouter({
2 history: createWebHistory(),
3 routes: routes
4})
当我们在vue项目中配置路由的时候,里面有一个属性叫history
,这个属性有两个值其中的createWebHashHistory() 就是我们常说的hash模式,在这个模式中我们的url路径中会多加一个#
除此之外与createWebHistory() 并无区别。
在简单回忆了一下router的hash是什么之后,接下来我们来根据router特点和hash模式来自己实现一下router中的hash模式,首先我们需要了解一个小知识点:
#/home 在浏览器的 url 中出现 # 号,#后面的内容会被看作是一个 hash 值,hash 值的变化不会引起页面的重新加载
在我们了解了这一个特点之后,我们就已经完成了router中的修改url之后浏览器不刷新这一特点,接下来我们只需要监听url变更,并且在修改url后页面刷新就行,接下来先给大家展示一下基础的html代码:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Document</title>
7</head>
8<body>
9 <ul>
10 <li><a href="#/home">首页</a></li>
11 <li><a href="#/about">关于</a></li>
12 </ul>
13
14 <div id="app"></div>
15
16 <script>
17
18 const routes = [
19 {
20 path: '/home',
21 component: () => {
22 return '<h2>首页页面</h2>'
23 }
24 },
25 {
26 path: '/about',
27 component: () => {
28 return '<h3>about页面</h3>'
29 }
30 },
31 ]
32 </script>
33</body>
34</html>
如果我们想要实现router剩下的两个特点,那么就需要对页面中url的变化进行监听,当页面中的url变化的时候,在页面执行对应的routes[x].component()
就可以在页面展示对应的组件。这个功能我们用一个函数routerView(localHash)
来实现加载对应路径组件这个功能:
1const app = document.getElementById('app')
2function routerView(localHash) {
3
4
5 const index = routes.findIndex((item) => {
6 return '#' + item.path === localHash
7 })
8 app.innerHTML = routes[index].component()
9}
要想实现上述监听url功能,我们就得用官方为我们提供的'hashchange'
事件,这个事件可以监听页面中hash值的变化,我们在url中用了#
将后面的值变成了hash值,而不同的页面就会展示不同的url就会让hash值进行改变,这样就能通过这个事件来加载对应的组件。
1
2window.addEventListener('hashchange', () => {
3
4 routerView(location.hash)
5})
同时我们需要在页面完成加载时,就展示对应的组件,完成这个功能才算实现了hash路由,这个功能我们通过官方提供的'DOMContentLoaded'
事件来监听页面加载完成,接下来我们来看完整代码:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Document</title>
7</head>
8<body>
9 <ul>
10 <li><a href="#/home">首页</a></li>
11 <li><a href="#/about">关于</a></li>
12 </ul>
13
14 <div id="app"></div>
15
16 <script>
17 const routes = [
18 {
19 path: '/home',
20 component: () => {
21 return '<h2>首页页面</h2>'
22 }
23 },
24 {
25 path: '/about',
26 component: () => {
27 return '<h3>about页面</h3>'
28 }
29 },
30 ]
31 const app = document.getElementById('app')
32 function routerView(localHash) {
33
34
35 const index = routes.findIndex((item) => {
36 return '#' + item.path === localHash
37 })
38 app.innerHTML = routes[index].component()
39 }
40
41
42 window.addEventListener('DOMContentLoaded', () => {
43 routerView(location.hash)
44 })
45
46 window.addEventListener('hashchange', () => {
47
48 routerView(location.hash)
49 })
50
51 </script>
52</body>
53</html>
3. history路由实现
通过上文我们知道了history路由的路径中是没有#
的,这样的话我们如果变换路径的时候那就必然会引起页面的刷新,我们实现history路由的时候要避免这一点。
首先我们页面中的基础html结构同上,我们同样需要一个routerView()
函数来实现对应url组件加载功能:
1const app = document.getElementById('app')
2function routerView() {
3 const index = routes.findIndex(item => {
4 return item.path === location.pathname
5 })
6 app.innerHTML = routes[index].component()
7}
接下来我们想要实现的效果是点击对应的a
标签就展示对应的组件,我们在点击a
标签的时候就会进行a
标签的默认跳转事件,这个时候我们只需要对要实现router效果的a
标签阻止它的默认行为,然后修改url路径后执行routerView()
函数即可:
history.pushState() 方法可以修改url,不会带来页面刷新
1
2let linkList = document.querySelectorAll('a')
3linkList.forEach(link => {
4 link.addEventListener('click', function(e) {
5
6 e.preventDefault()
7
8 history.pushState(null, '', this.getAttribute('href'))
9 routerView()
10 })
11})
在实现了url变更展示对应组件和页面不刷新两点功能之后,还有一个小点得注意一下,当我们点击浏览器左上角前进后退的时候,页面也要展示对应url的组件,仅靠上述代码是实现不了的,这时候我们就需要使用'popstate'
事件来实现这个效果,接下来直接给大家展示完整代码:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Document</title>
7</head>
8<body>
9 <ul>
10 <li><a href="/home">首页</a></li>
11 <li><a href="/about">关于</a></li>
12 </ul>
13
14 <div id="app"></div>
15
16 <script>
17 const routes = [
18 {
19 path: '/home',
20 component: () => {
21 return '<h2>首页页面</h2>'
22 }
23 },
24 {
25 path: '/about',
26 component: () => {
27 return '<h3>about页面</h3>'
28 }
29 },
30 ]
31
32
33 window.addEventListener('popstate', function () {
34 routerView()
35 })
36
37 let linkList = document.querySelectorAll('a')
38 linkList.forEach(link => {
39 link.addEventListener('click', function(e) {
40
41 e.preventDefault()
42
43 history.pushState(null, '', this.getAttribute('href'))
44 routerView()
45 })
46 })
47
48 const app = document.getElementById('app')
49 function routerView() {
50 const index = routes.findIndex(item => {
51 return item.path === location.pathname
52 })
53 app.innerHTML = routes[index].component()
54 }
55 </script>
56</body>
57</html>
总结
hash 路由: #/home 在浏览器的 url 中出现 # 号,#后面的内容会被看作是一个 hash 值,hash 值的变化不会引起页面的重新加载,从而直接监听hashchange事件来判断要展示的对应的组件
history 路由: 首先阻止 a 标签的默认行为,然后使用 history.pushState() 方法来修改 url 路径,它是不会引起页面的重新加载的,在url变更后,读取本地的 pathname 来判断要展示的对应的组件