前言
说起路由,那么就先来聊聊上古时期的网页是怎么渲染的吧。服务端工程师在后台把页面都构建好,根据浏览器的请求地址,去匹配相应的页面,输出的都是已经生成好的HTML,点击跳转都会重新刷新出一个新的页面。
为了提升用户体验,单页应用(SPA)应运而生,良好的体验,丝般柔滑的页面切换,让人心旷神怡。既然页面由客户端渲染了,路由也随之而来。现在用 React 或 Vue 做个页面,谁不加个 Router 包呢。用的是爽,大家知道其中的原理吗?
hash
大家常常看到的hash就是浏览器上的a标签作为锚点去使用,点击的时候浏览器地址改变了 #/xxx 这种新式,并且页面是不会刷新的。上面说的 React 和 Vue 所用的路由包,也有hash的形式去切换路由。浏览器原生的提供了一个监听事件 hashchange ,a 标签改变 url、浏览器的前进后退、window.location 改变地址栏的地址,都会触发 hashchange 事件, 通过这个事件去获取 localtion.hash,继而去匹配相应的组件。代码大致如下:
<!DOCTYPE html><html><head><title>hash路由</title></head><body><div><ul><li><a href="#/page1">page1</a></li><li><a href="#/page2">page2</a></li></ul><div id="route-view"></div></div><script type="text/javascript">// 下面为hash的路由实现方式// 第一次加载的时候,不会执行hashchange监听事件,默认执行一次window.addEventListener('DOMContentLoaded', Load)window.addEventListener('hashchange', HashChange)var routeView = nullfunction Load() {routeView = document.getElementById('route-view')HashChange()}function HashChange() {switch(location.hash) {case '#/page1':routeView.innerHTML = 'page1'returncase '#/page2':routeView.innerHTML = 'page2'returndefault:routeView.innerHTML = 'page1'return}}</script></body></html>
history
**
通过 history 去控制路由的话,那就比较麻烦了,因为 history 提供的 popstate 监听事件监听不到 pushState、replaceState、a 标签这三种形式的改变,浏览器的前进后退是可以监听到的。有什么好的解决方案呢?遍历页面上的所有 a 标签,然后阻止它的默认事件,加上点击事件回调函数,在函数内,获取 a 标签的 href 属性值,再通过 pushState 去改变浏览器上的 pathname,最后手动执行一次 popstate 监听的回调函数,匹配相应的路由。代码如下:
<!DOCTYPE html><html><head><title>hash路由</title></head><body><div><ul><li><a href="/page1">page1</a></li><li><a href="/page2">page2</a></li></ul><div id="route-view"></div></div><script type="text/javascript">// 下面为history的路由实现方式window.addEventListener('DOMContentLoaded', Load)window.addEventListener('popstate', PopChange)var routeView = nullfunction Load() {routeView = document.getElementById('route-view')PopChange()var aList = document.querySelectorAll('a[href]')aList.forEach(event => event.addEventListener(function(e) {e.preventDefault() //阻止a标签的默认事件var href = e.getAttribute('href')history.pushState(null, '', href)PopChange()}))}function PopChange() {switch(location.pathname) {case '/page1':routeView.innerHTML = 'page1'returncase '/page2':routeView.innerHTML = 'page2'returndefault:routeView.innerHTML = 'page1'return}}</script></body></html>
注意,以下代码不能直接打开浏览器访问,因为地址是本地地址,会有问题,需要启动服务打开
说了这么多,大家也应该对 Link 和 a 标签的区别有所了解了。就是默认情况下用 a 标签,默认事件是不会被阻止的,所以点击 a 标签,页面会刷新。而 Link 内部阻止的 a 标签的默认事件,转而去获取它的 href 值去 pushState 改变 pathname,不会引起页面的刷新。
