前言
本章和node的关系不大,不过的确是服务端开发的必备前端知识。
路由经历了不同的发展阶段
后端路由又可称之为服务器端路由,因为对于服务器来说,当接收到客户端发来的HTTP请求,就会根据所请求的相应URL,来找到相应的映射函数,然后执行该函数,并将函数的返回值发送给客户端。对于最简单的静态资源服务器,可以认为,所有URL的映射函数就是一个文件读取操作。对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理,等等。然后根据这些读取的数据,在服务器端就使用相应的模板来对页面进行渲染后,再返回渲染完毕的页面。这种方式在早期的前端开发中非常普遍,它的好处与坏处都很明显:- 好处:安全性好,SEO好。
- 缺点:加大服务器的压力,不利于用户体验,代码冗合。
也正是由于后端路由还存在着自己的不足,前端路由才有了属于自己的一片天地与发展的空间。对于前端路由来说,路由的映射函数通常是进行一些DOM的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。前端路由主要有以下两种实现方案:
当然,前端路由也存在缺陷:使用浏览器的前进,后退键时会重新发送请求,来获取数据,没有合理地利用缓存。但总的来说,现在前端路由已经是实现路由的主要方式了,我们常用的诸如vue-router等前端框架的路由控制都是基于前端路由进行开发的(vue-router 4.x 又多了一种 Memory 模式),因此将前端路由进行一个了解还是很有必要的。
代码实现 重原理 真正的框架路由库肯定还有诸多细节
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>client router</title> </head> <body> <p>路由控制由后端移交给前端是前后端分离的重要标志</p> <p>SPA能够模拟多页面应用的效果, 归功于其前端路由机制。</p> <ul> <li style="color: red">hash router</li> <li><a href="#/index">index</a></li> <li><a href="#/about">about</a></li> <li><a href="#/me">me</a></li> </ul> <ul id="history"> <li style="color: red">history router</li> <li><a href="/index">index</a></li> <li><a href="/about">about</a></li> <li><a href="/me">me</a></li> </ul> <div id="router-view"> <!-- 内容加载区域 --> </div> </body> <script> class HashRouter { constructor(params) { this.routes = params.routes; this.routePath = ''; // this 丢失,相当于把this.renderView的指向赋值给load事件的回调,导致renderView执行时this指向错误 // window.addEventListener('load', this.renderView, false); // window.addEventListener('hashchange', this.renderView, false); window.addEventListener('load', () => this.renderView(), false); window.addEventListener('hashchange', () => this.renderView(), false); } renderView() { const curPath = location.hash.slice(1); const routes = this.routes.map(cur => cur.path); if (!routes.includes(curPath)) { this.routePath = '/index'; // 触发hashchange location.hash = '#' + this.routePath; return; } this.routePath = curPath; document.querySelector('#router-view').innerHTML = this.routes.find(cur => cur.path === this.routePath).view; } } new HashRouter({ routes: [ { path: '/index', view: '主页' }, { path: '/about', view: '关于' }, { path: '/me', view: '我的' } ] }); // HistoryRouter 需要服务器做配置才能实现真正的前端路由,比如点击刷新页面的行为 // see https://router.vuejs.org/zh/guide/essentials/history-mode.html class HistoryRouter { constructor(params) { this.routes = params.routes; this.routePath = ''; document.querySelector('#history').addEventListener('click', e => { e.preventDefault(); try { let routePath = new URL(e.target.href).pathname; this.renderView(routePath); // 第三个参数代表新历史条目的 URL 如果该参数没有指定,则将其设置为当前文档的 URL。 history.pushState({ path: routePath }, '', routePath); } catch (err) { throw new Error(err); } }); window.addEventListener('load', () => this.renderView('/'), false); window.addEventListener('popstate', e => this.renderView(e), false); } renderView(routePath) { this.routePath = routePath; if (typeof routePath === 'object') { this.routePath = routePath.state?.path ?? '/'; } const routes = this.routes.map(cur => cur.path); if (!routes.includes(this.routePath)) { this.routePath = '/index'; } document.querySelector('#router-view').innerHTML = this.routes.find(cur => cur.path === this.routePath)?.view; } } new HistoryRouter({ routes: [ { path: '/index', view: '主页' }, { path: '/about', view: '关于' }, { path: '/me', view: '我的' } ] }); // 从理论上来说hash和history路由并不矛盾 // 你的SPA既可以使用hash也可以使用history 就像这个例子 同时new两个实例也能实现预期的导航 (only you hold ok ...) </script> </html>
vue-router 实现页面路由
2024-01-14 00:20:05 64 阅读