前端路由简介
前端路由机制主要是应用在SPA单页面架构中
- 页面交互快,并且是无刷新的,改变URL不会向服务器发起请求(改变URL指的是js变化,不是页面重新加载)
- js中有API支持,能监听url变化,并且解析内容,再走对应的逻辑操作
- 前端路由的实现主流方案有两种,分别是Hash和History
虽然前端路由用户体验更好,更快,但鱼和熊掌不可兼得,得到了更好的东西,总会失去一些其它东西,比如:
- 初次加载耗时相对增多(需要开发人员做优化)
- 对搜索引擎SEO不友好(需要单独处理,通过服务端渲染SSR,让搜索引擎抓取更多有用的信息)
1. 路由结构(Location的一些属性)
- protocal: 协议头
- hostname: 域名
- port: 端口
- pathname: 路径名称
- seach: 搜索参数
- hash: hash值
2. 获取路由信息
- 方法:window.localhost
两种方案代码实现
页面效果(完整代码在最后)
目录结构(主要都在src目录下)
App.vue:
HTML内容main.js:
在这生成hash实例和history实例,点击li时,跳转对应路由hash.js:
实现hash逻辑share.js:
hash和history公用的方法history.js:
实现history逻辑routerList.js:
配置路由list
共用的代码逻辑
App.vue
<p>
: 展示当前路径名称<button>
: 切换hash和history模式<li>
: 当前所有可选的路由
<div>
<p id="pathDiv"></p>
<p><button id="btn" data-type='HASH'>当前使用:HASH路由</button></p>
</div>
<ul class="router-list">
<li data-url="1">前进</li>
<li data-url="-1">后退</li>
<li id="rq"> 重置路由 </LI>
</ul>
main.js
1. 通过路由列表生成对应可点击的li列表
import { ROUTELIST } from "./routerList";
import HashRouter from "./hash";
import HistoryRouter from "./history";
let ROUTER_MODE = 'HASH'; // 路由方式,默认HASH
const routerListDom = document.querySelector('.router-list');
// 循环生成li列表
ROUTELIST.forEach((e, index) => {
if (index > 1 ) {
const li = document.createElement('li');
li.setAttribute('data-url', e.path);
li.innerHTML = e.name
routerListDom.append(li);
}
});
2. 创建一个模拟路由类
class AnalogRouter {
constructor(params) {
const { mode, routeList } = params;
// 根据使用类型,创建对应的实例
this.router =
mode === 'HASH' ?
new HashRouter(routeList) :
new HistoryRouter(routeList);
}
// 根据path跳转地址
push(path) {
this.router.push(path);
}
// 替换地址
replace(path) {
this.router.replace(path);
}
// 根据index跳转地址
go(num) {
this.router.go(num);
}
}
3. 创建页面的路由实例,并且绑定li点击事件,处理逻辑
let windowRouter = new AnalogRouter({
mode: ROUTER_MODE,
routeList: ROUTELIST
});
routerListDom.addEventListener('click', e => {
const { nodeName, id } = e.target;
// 只有点击li时才继续
if (nodeName !== 'LI') return;
// 点击重置路由时
if ( id === 'rq') {
windowRouter.replace('/')
} else {
const url = e.target.getAttribute('data-url');
// 存在【/】时,代表是路径
// 否则是传入数字
url.includes('/') ? windowRouter.push(url) : windowRouter.go(url);
}
});
4. Hash和History共用的路由配置
export default class shareRouter {
constructor(routerList) {
this.router = routerList;
}
load(hash){
// 1、判断是否存在路由
let path = this.router.find(e => e.path === hash);
// 2、不存在则显示404
path = path || this.router.find(e => e.path === '*');
document.getElementById('pathDiv').innerHTML = `${path.component}`;
}
// 后退前进方法
go(num) {
window.history.go(num)
}
}
Hash模式的实现
1. 依赖Location的一些方法
- Location.assign:改变url地址,会生成一条新的历史记录
- Location.replace: 改变url地址,但是不会生成新的历史记录
- Location.onhashchange: 监听事件,当url路由hash值发生变化时,会触发该方法
- Location.toString: 与属性window.location.href的效果相同,但是无法拥有location.href修改url的能力,相当于location.href的只读版本。
2. 代码逻辑
- 继承共用的shareRouter类,添加对应的push、replace事件
- 通过onhashchange事件监听路由变化
- 通过Location.hash更改url,并增加一条新的历史记录
- 通过Location.replace替换url,不会新增历史记录
import shareRouter from "./share";
export default class HashRouter extends shareRouter {
constructor(router) {
super(router);
this.hashChange();
// 监听hash改变事件
window.addEventListener('hashchange', e => {
this.hashChange();
})
}
// hash改变事件
hashChange() {
this.load(this.getHash())
}
// 获取当前的hash值,在页面展示当前url
getHash() {
const hash = window.location.hash;
return hash ? hash.slice(1) : '/';
}
// push事件
push (path) {
window.location.hash = path;
}
// 获取默认页的url
getUrl(path) {
const { href } = window.location;
const index = href.indexOf('#');
const base = index >= 0 ? href.slice(0, index) : href;
return `${base}#${path}`;
}
// 替换事件
replace(path) {
window.location.replace(this.getUrl(path))
}
}
History模式的实现
依赖History API的一些方法
- history.forward: 向前移动一页,和history.go(1)用法一致
- history.back: 向后移动一页,和history.go(-1)用法一致
- history.go: 从访问记录中加载特定页面,取决于传入的参数(注:不传或者传0,会重载当前页面)
- history.pushState: HTML5新增规范,产生一条新的url历史记录,不会触发popstate事件
: - history.replaceState:HTML5新增规范,与history.pushState功能相似,但它不会产生新的url历史记录,而是修改当前的访问记录,不会触发popstate事件
- window.onpopstate: 前进后退时,会触发该事件
2. 代码逻辑
export default class HistoryRouter extends shareRouter {
constructor(router) {
super(router);
this.pathChange();
// 参考MDS文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event
window.addEventListener('popstate', e => {
console.log('我触发了', e)
this.pathChange();
})
}
// 路由改变事件
pathChange() {
this.load(this.getUrl())
}
// 获取当前的hash值
getUrl() {
const { pathname = '/' } = window.location;
return pathname;
}
// push事件
push(path) {
history.pushState({ time: +new Date() }, '', path)
this.pathChange();
}
// 替换事件
replace(path) {
history.replaceState(null, null, path);
this.pathChange();
}
}
总结
两种模式对比
类型 | Hash | History |
---|---|---|
外观 | 带#号,url会比较丑 | 外观更好看 |
影响 | 占用锚点的功能 | 不占用 |
服务端支持 | 无需服务端支持 | 需要服务端配置 |
监听事件 | onhashchange() | onpopstate() |
扩展 | 只能添加短字符串 | 可通过pushState()添加任意类型的数据到记录中,以及设置title属性 |
使用场景选择(个人更推荐History)
Hash:相当于a标签的锚点,切换页面实际上是对锚点的修改,就像div的显示和隐藏,通过onhashchange来监听hash的改变,不会发生页面跳转行为。
History:是浏览器的API,同样保存了切换状态,使用history.pushState会向浏览器添加一条记录。页面内容改变,与Hash的div显示和隐藏一样,将对应的div操作与pushState结合使用。
完整代码
- gitHub :https://github.com/babachao/javascript-router
- 码云 :https://gitee.com/huchaoMan/javascript-router
参考资料
- [1] MDN-Location: https://developer.mozilla.org/zh-CN/docs/Web/API/Location
- [2] MDN-History API: https://editor.mdnice.com/?outId=9bdb5690beb5440c805bcf1a4da285bc