javascript: 解析前端路由,自己实现一个前端路由机制

前端路由简介

前端路由机制主要是应用在SPA单页面架构中

  • 页面交互快,并且是无刷新的,改变URL不会向服务器发起请求(改变URL指的是js变化,不是页面重新加载)
  • js中有API支持,能监听url变化,并且解析内容,再走对应的逻辑操作
  • 前端路由的实现主流方案有两种,分别是HashHistory

虽然前端路由用户体验更好,更快,但鱼和熊掌不可兼得,得到了更好的东西,总会失去一些其它东西,比如:

  1. 初次加载耗时相对增多(需要开发人员做优化)
  2. 对搜索引擎SEO不友好(需要单独处理,通过服务端渲染SSR,让搜索引擎抓取更多有用的信息)

1. 路由结构(Location的一些属性)

- protocal: 协议头
- hostname: 域名
- port: 端口
- pathname: 路径名称
- seach: 搜索参数
- hash: hash值

2. 获取路由信息

  • 方法:window.localhost
    方法: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. 代码逻辑

  1. 继承共用的shareRouter类,添加对应的push、replace事件
  2. 通过onhashchange事件监听路由变化
  3. 通过Location.hash更改url,并增加一条新的历史记录
  4. 通过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的一些方法

  1. history.forward: 向前移动一页,和history.go(1)用法一致
  2. history.back: 向后移动一页,和history.go(-1)用法一致
  3. history.go: 从访问记录中加载特定页面,取决于传入的参数(注:不传或者传0,会重载当前页面)
  4. history.pushState: HTML5新增规范,产生一条新的url历史记录,不会触发popstate事件
    :
  5. history.replaceState:HTML5新增规范,与history.pushState功能相似,但它不会产生新的url历史记录,而是修改当前的访问记录,不会触发popstate事件
  6. 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();
 }
}

总结

两种模式对比

类型HashHistory
外观带#号,url会比较丑外观更好看
影响占用锚点的功能不占用
服务端支持无需服务端支持需要服务端配置
监听事件onhashchange()onpopstate()
扩展只能添加短字符串可通过pushState()添加任意类型的数据到记录中,以及设置title属性

使用场景选择(个人更推荐History)

Hash:相当于a标签的锚点,切换页面实际上是对锚点的修改,就像div的显示和隐藏,通过onhashchange来监听hash的改变,不会发生页面跳转行为。

History:是浏览器的API,同样保存了切换状态,使用history.pushState会向浏览器添加一条记录。页面内容改变,与Hash的div显示和隐藏一样,将对应的div操作与pushState结合使用。

完整代码

参考资料

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值