前端路由的实现原理

发布于 2023-11-13 07:04:43 字数 3734 浏览 53 评论 0

在使用 Vue、React 等前端框架时,我们都会发现项目中只有一个 HTML 文件,并且在该 HTML 中都存在一个根标签,起到了类似于容器的作用。容器内部的内容就由我们后续编写的每个视图决定,页面的切换就是容器中视图的切换。

前端路由的实现原理简单来说,就是在不跳转或者刷新页面的前提下,为 SPA 应用中的每个视图匹配一个特殊的 URL,之后的刷新、前进、后退等操作均通过这个特殊的 URL 实现。为实现上述要求,需要满足:

改变 URL 且不会向服务器发起请求;

可以监听到 URL 的变化,并渲染与之匹配的视图。

主要有 Hash 路由和 History 路由两种实现方式。下文对两者的基本原理进行简单介绍,并分别实现了一个简易的路由 Demo。

Hash 路由

原理就是通过键值对的形式保存路由及对应要执行的回调函数,当监听到页面 hash 发生改变时,根据最新的 hash 值调用注册好的回调函数,即改变页面。

创建路由

class Routers{
  constructor(){
    // 保存路由信息
    this.routes = {};
    this.currentUrl = '';
    window.addEventListener('load', this.refresh, false);
    window.addEventListener('hashchange', this.refresh, false);
  }

  // 用于注册路由的函数
  route = (path, callback) => {
    this.routes[path] = callback || function(){};
  }

  // 监听事件的回调,负责当页面 hash 改变时执行对应 hash 值的回调函数
  refresh = () => {
    this.currentUrl = location.hash.slice(1) || '/';
    this.routes[this.currentUrl]();
  }
}

window.Router = new Routers();

注册路由

使用 route 方法添加对应的路由及其回调函数即可。以下代码实现了一个根据不同 hash 改变页面颜色的路由,模拟了页面的切换,在实际的 SPA 应用中,对应的就是页面内容的变化了。

var content = document.querySelector('body');

function changeBgColor(color){
  content.style.background = color;
}

// 添加路由
Router.route('/', () => {
  changeBgColor('yellow');
});
Router.route('/red', () => {
  changeBgColor('red');
});
Router.route('/green', () => {
  changeBgColor('green');
});
Router.route('/blue', () => {
  changeBgColor('blue');
});

History 路由

在 H5 之前,浏览器的 history 仅支持页面之前的跳转,包括前进和后退等功能。

在 HTML5 中,新增以下 API:

history.pushState();			// 添加新状态到历史状态栈
history.replaceState();		// 用新状态代替当前状态
history.state;						// 获取当前状态对象

history.pushState()和 history.replaceState()均接收三个参数:

  • state:一个与指定网址相关的状态对象,popstate 事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填 null。
  • title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填 null。
  • url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址

由于 history.pushState()和 history.replaceState()都具有在改变页面 URL 的同时,不刷新页面的能力,因此也可以用来实现前端路由。

创建路由类

class Routers{
  constructor(){
    this.routes = {};
    window.addEventListener('popstate', e => {
      const path = e.state && e.state.path;
      this.routes[path] && this.routes[path]();
    })
  }

  init(path){
    history.replaceState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }

  route(path, callback){
    this.routes[path] = callback || function(){};
  }

  go(path){
    history.pushState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }
}

window.Router = new Routers();

注册路由

function changeBgColor(color){
  content.style.background = color;
}

Router.route(location.pathname, () => {
  changeBgColor('yellow');
});
Router.route('/red', () => {
  changeBgColor('red');
});
Router.route('/green', () => {
  changeBgColor('green');
});
Router.route('/blue', () => {
  changeBgColor('blue');
});

const content = document.querySelector('body');
Router.init(location.pathname);

触发事件

在使用 hash 实现的路由中,我们通过 hashchange 事件来监听 hash 的变化,但是上述代码中 history 的改变本身不会触发任何事件,因此无法直接监听 history 的改变来改变页面。因此,对于不同的情况,我们选择不同的解决方案:

  • 点击浏览器的前进或者后退按钮:监听 popstate 事件,获取相应路径并执行回调函数
  • 点击 a 标签:阻止其默认行为,获取其 href 属性,手动调用 history.pushState(),并执行相应回调。
const ul = document.querySelector('ul');

ul.addEventListener('click', e => {
  if(e.target.tagName === 'A'){
    e.preventDefault();
    Router.go(e.target.getAttribute('href'));
  }
})

对比

基于 hash 的路由:

缺点:

  • 看起来比较丑
  • 会导致锚点功能失效

优点:

  • 兼容性更好
  • 无需服务器配合

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

站稳脚跟

暂无简介

文章
评论
29 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文