前言

说起路由,那么就先来聊聊上古时期的网页是怎么渲染的吧。服务端工程师在后台把页面都构建好,根据浏览器的请求地址,去匹配相应的页面,输出的都是已经生成好的HTML,点击跳转都会重新刷新出一个新的页面。
为了提升用户体验,单页应用(SPA)应运而生,良好的体验,丝般柔滑的页面切换,让人心旷神怡。既然页面由客户端渲染了,路由也随之而来。现在用 ReactVue 做个页面,谁不加个 Router 包呢。用的是爽,大家知道其中的原理吗?

不妨在此先说一说前端控制路由的两种形式。
**

hash

大家常常看到的hash就是浏览器上的a标签作为锚点去使用,点击的时候浏览器地址改变了 #/xxx 这种新式,并且页面是不会刷新的。上面说的 ReactVue 所用的路由包,也有hash的形式去切换路由。浏览器原生的提供了一个监听事件 hashchange ,a 标签改变 url、浏览器的前进后退、window.location 改变地址栏的地址,都会触发 hashchange 事件, 通过这个事件去获取 localtion.hash,继而去匹配相应的组件。代码大致如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>hash路由</title>
  5. </head>
  6. <body>
  7. <div>
  8. <ul>
  9. <li><a href="#/page1">page1</a></li>
  10. <li><a href="#/page2">page2</a></li>
  11. </ul>
  12. <div id="route-view"></div>
  13. </div>
  14. <script type="text/javascript">
  15. // 下面为hash的路由实现方式
  16. // 第一次加载的时候,不会执行hashchange监听事件,默认执行一次
  17. window.addEventListener('DOMContentLoaded', Load)
  18. window.addEventListener('hashchange', HashChange)
  19. var routeView = null
  20. function Load() {
  21. routeView = document.getElementById('route-view')
  22. HashChange()
  23. }
  24. function HashChange() {
  25. switch(location.hash) {
  26. case '#/page1':
  27. routeView.innerHTML = 'page1'
  28. return
  29. case '#/page2':
  30. routeView.innerHTML = 'page2'
  31. return
  32. default:
  33. routeView.innerHTML = 'page1'
  34. return
  35. }
  36. }
  37. </script>
  38. </body>
  39. </html>

history

**
通过 history 去控制路由的话,那就比较麻烦了,因为 history 提供的 popstate 监听事件监听不到 pushState、replaceState、a 标签这三种形式的改变,浏览器的前进后退是可以监听到的。有什么好的解决方案呢?遍历页面上的所有 a 标签,然后阻止它的默认事件,加上点击事件回调函数,在函数内,获取 a 标签的 href 属性值,再通过 pushState 去改变浏览器上的 pathname,最后手动执行一次 popstate 监听的回调函数,匹配相应的路由。代码如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>hash路由</title>
  5. </head>
  6. <body>
  7. <div>
  8. <ul>
  9. <li><a href="/page1">page1</a></li>
  10. <li><a href="/page2">page2</a></li>
  11. </ul>
  12. <div id="route-view"></div>
  13. </div>
  14. <script type="text/javascript">
  15. // 下面为history的路由实现方式
  16. window.addEventListener('DOMContentLoaded', Load)
  17. window.addEventListener('popstate', PopChange)
  18. var routeView = null
  19. function Load() {
  20. routeView = document.getElementById('route-view')
  21. PopChange()
  22. var aList = document.querySelectorAll('a[href]')
  23. aList.forEach(event => event.addEventListener(function(e) {
  24. e.preventDefault() //阻止a标签的默认事件
  25. var href = e.getAttribute('href')
  26. history.pushState(null, '', href)
  27. PopChange()
  28. }))
  29. }
  30. function PopChange() {
  31. switch(location.pathname) {
  32. case '/page1':
  33. routeView.innerHTML = 'page1'
  34. return
  35. case '/page2':
  36. routeView.innerHTML = 'page2'
  37. return
  38. default:
  39. routeView.innerHTML = 'page1'
  40. return
  41. }
  42. }
  43. </script>
  44. </body>
  45. </html>

注意,以下代码不能直接打开浏览器访问,因为地址是本地地址,会有问题,需要启动服务打开

说了这么多,大家也应该对 Link 和 a 标签的区别有所了解了。就是默认情况下用 a 标签,默认事件是不会被阻止的,所以点击 a 标签,页面会刷新。而 Link 内部阻止的 a 标签的默认事件,转而去获取它的 href 值去 pushState 改变 pathname,不会引起页面的刷新。