上篇文章简单介绍了html5的history api,这篇将就进入实战环节。看看在history api的辅助下,目前流行的前端SPA框架路由是怎么实现的。
1.什么是SPA
首先,什么事spa呢?一句话概括:spa就是单页面应用,即:single page application。通过看源码就可以发现,整个网站就只有一个html文件。
那么,spa有什么优势呢?为什么前端要采用这种技术选型呢?小编总结了下,觉得有点有一下几点:
a.减小服务器压力。 如果不用SPA,那么我们每次切换页面的时候,就会向服务器发送一个请求,服务器返回一个html文件;但是如果使用了SPA,在切换时,不需要请求服务器,只要通过本地的js来切换即可。并且服务器端就不需要配置路由,完全做到了前后端分离,这对于分工合作的意义可想而知。
b.增强用户体验,增加app的使用流畅性。 做过spa的同学都知道,使用spa之后,页面在切换的时候非常流畅,完全没有那种不断刷新的感觉,而是非常快的就有了响应,因为js运行速度很快,所以js在做本地路由的时候,就会非常快。
然则,牛逼的spa就没有缺点了吗?答案是有的。
a.SEO问题没有html抓不到什么。。。
b.刚开始的时候加载可能慢很多
c.用户操作需要写逻辑,前进、后退等;
d.页面复杂度提高很多,复杂逻辑难度成倍
a,b两点可以通过服务端渲染克服。目前流行的前端框架也已经能很好的支持服务端渲染。d却要根据具体业务需求合理的进行页面分割、组件划分。至于c,咱们就结合昨天所讲的history api看看具体如何解决。
2.SPA路由实现有哪些
目前来说,无论是vue,还是react,spa的路由实现方式无非就是以下两者:
1.hash方式。 使用location.hash和hashchange事件实现路由。
2.history api。使用html5的history api实现,主要就是popState事件等。
hash用的还是比较多的,但是这种方式的url会比较丑陋,会出现 #; 而history api就可以很好的解决这个问题,但是需要后端配置,并且由于是html5中的api,所以兼容性还不是很好,用的时候需要确定你的用户在做决定。
2.1 HashRouter
具体实现代码如下:
function HashRouter() { this.routes = {}; this.currentUrl = ''; }; HashRouter.prototype.init = function() { window.addEventListener( 'load', this.refresh.bind( this ), false ); window.addEventListener( 'hashchange', this.refresh.bind( this ), false ); }; HashRouter.prototype.refresh = function () { this.currentUrl = location.hash.slice( 1 ) || '/'; this.routes[ this.currentUrl ](); }; HashRouter.prototype.route = function( path, callback ) { this.routes[ path ] = callback || function(){}; };
1.首先定义一个HashRouter构造函数,currentUrl存放当前地址,routes存放历史地址及其各自对应的回调函数。
2.route方法用于向hashRouter对象注册路由地址和回调函数
3.refresh方法会在路由切换时执行刷新页面
4.在路由注册完成后会执行init方法,以监听地址栏hash值的变化
2.2 BrowserRouter
上面使用的hash方法实现路由固然不错,但是也是存在一定的问题的,就是太丑~ 如果在微信中使用,看不到url倒也无所谓,但是如果在一般的浏览器中使用就会遇到问题了。所以这里使用 history api。具体代码如下:
<script type="text/javascript"> function MyBrowserRouter() { this.currentRoute = ''; this.routes = {}; this.init(); } MyBrowserRouter.prototype.route = function( path, callback ) { this.routes[ path ] = function( type ) { if ( type === 1 ) { history.pushState( { path }, path, path ); } else if ( type === 2 ) { history.replaceState( { path }, path, path ); } callback(); } }; MyBrowserRouter.prototype.refresh = function( path, type ) { this.routes[ path ]( type ); }; MyBrowserRouter.prototype.init = function() { let links = document.querySelectorAll( '.history-link' ); window.addEventListener( 'load', () => { this.currentRoute = location.href.slice( location.href.indexOf( '/', 8 ) ); this.refresh( this.currentRoute, 2 ); } ); window.addEventListener( 'popstate', () => { this.currentRoute = history.state.path; this.refresh( this.currentRoute, 2 ); }, false ); for ( let i = 0; i < links.length; i++ ) { links[ i ].onclick = ( e ) => { e.preventDefault(); this.currentRoute = e.target.getAttribute( 'href' ); this.refresh( this.currentRoute, 2 ); } } }; </script>