logo

手写 Hash Router:从原理到实现

作者:宇宙中心我曹县2025.09.19 12:47浏览量:0

简介:本文深入解析Hash Router的核心原理,结合代码示例详细说明如何手动实现一个轻量级路由系统,涵盖监听机制、路由匹配、动态参数处理等关键环节。

一、Hash Router 的核心原理

Hash Router(哈希路由)是前端路由实现中最基础的一种方案,其核心原理依赖于URL的哈希部分(即#之后的内容)的变化。当URL哈希改变时,浏览器不会重新加载页面,而是触发hashchange事件,开发者可以通过监听该事件实现单页应用(SPA)的路由切换。

1.1 哈希值与浏览器行为

URL的哈希部分原本用于指定页面内的锚点位置,但现代前端框架利用其“无刷新”特性实现路由控制。例如:

  • https://example.com/#/home
  • https://example.com/#/profile

浏览器会加载同一HTML文件,但哈希值的差异可通过JavaScript捕获,进而动态更新页面内容。

1.2 哈希变化的触发方式

哈希值可通过以下方式改变:

  1. 用户点击链接<a href="#/about">About</a>
  2. 代码修改location.hashwindow.location.hash = '/contact'
  3. 浏览器前进/后退:触发历史记录中的哈希变化

1.3 事件监听机制

通过window.addEventListener('hashchange', callback),开发者可在哈希变化时执行路由匹配逻辑。结合window.location.hash可获取当前哈希值,解析后决定渲染哪个组件。

二、手写 Hash Router 的实现步骤

2.1 初始化路由容器与配置

首先需要定义路由表,存储路径与对应组件的映射关系:

  1. class HashRouter {
  2. constructor(options) {
  3. this.routes = options.routes || {}; // 路由配置 { '/home': HomeComponent }
  4. this.currentPath = '';
  5. this.init();
  6. }
  7. init() {
  8. // 监听hashchange事件
  9. window.addEventListener('hashchange', this.onHashChange.bind(this));
  10. // 初始路由匹配
  11. this.onHashChange();
  12. }
  13. }

2.2 哈希变化处理逻辑

onHashChange方法中,需完成以下操作:

  1. 获取当前哈希值(去掉#前缀)
  2. 匹配路由表中的组件
  3. 渲染对应组件到DOM容器

    1. onHashChange() {
    2. this.currentPath = window.location.hash.slice(1) || '/';
    3. const Component = this.routes[this.currentPath];
    4. if (Component) {
    5. const container = document.getElementById('app');
    6. container.innerHTML = '';
    7. container.appendChild(new Component().render());
    8. } else {
    9. console.error('404: Route not found');
    10. }
    11. }

2.3 动态路由参数处理

实际场景中,路由可能包含动态参数(如/user/:id)。需实现参数解析功能:

  1. // 扩展路由配置
  2. {
  3. '/user/:id': UserComponent
  4. }
  5. // 解析逻辑
  6. parsePath(path) {
  7. const regex = /^\/user\/([^\/]+)$/;
  8. const match = path.match(regex);
  9. if (match) {
  10. return { component: this.routes['/user/:id'], params: { id: match[1] } };
  11. }
  12. return null;
  13. }

2.4 路由导航方法

提供编程式导航API,允许代码中触发路由跳转:

  1. class HashRouter {
  2. // ...其他代码
  3. push(path) {
  4. window.location.hash = path;
  5. }
  6. replace(path) {
  7. const oldHash = window.location.hash;
  8. window.location.hash = path;
  9. // 替换历史记录需结合history.replaceState(需额外处理)
  10. }
  11. }

三、完整实现示例

  1. class HashRouter {
  2. constructor(options) {
  3. this.routes = options.routes || {};
  4. this.container = options.container || 'app';
  5. this.currentPath = '';
  6. this.init();
  7. }
  8. init() {
  9. window.addEventListener('hashchange', this.onHashChange.bind(this));
  10. this.onHashChange();
  11. }
  12. onHashChange() {
  13. this.currentPath = window.location.hash.slice(1) || '/';
  14. const result = this.parsePath(this.currentPath);
  15. if (result) {
  16. const { component: Component, params } = result;
  17. document.getElementById(this.container).innerHTML = '';
  18. const instance = new Component(params);
  19. document.getElementById(this.container).appendChild(instance.render());
  20. } else {
  21. console.error('404: Route not found');
  22. }
  23. }
  24. parsePath(path) {
  25. for (const [routePattern, Component] of Object.entries(this.routes)) {
  26. const params = {};
  27. const regexStr = routePattern
  28. .replace(/\/:([^\/]+)/g, (match, paramName) => {
  29. params[paramName] = true;
  30. return '/([^/]+)';
  31. });
  32. const regex = new RegExp(`^${regexStr}$`);
  33. const match = path.match(regex);
  34. if (match) {
  35. const actualParams = {};
  36. Object.keys(params).forEach((key, index) => {
  37. actualParams[key] = match[index + 1];
  38. });
  39. return { component: Component, params: actualParams };
  40. }
  41. }
  42. return null;
  43. }
  44. push(path) {
  45. window.location.hash = path;
  46. }
  47. }
  48. // 使用示例
  49. class Home {
  50. render() {
  51. const div = document.createElement('div');
  52. div.textContent = 'Home Page';
  53. return div;
  54. }
  55. }
  56. class User {
  57. constructor(params) {
  58. this.id = params.id;
  59. }
  60. render() {
  61. const div = document.createElement('div');
  62. div.textContent = `User ID: ${this.id}`;
  63. return div;
  64. }
  65. }
  66. const router = new HashRouter({
  67. routes: {
  68. '/': Home,
  69. '/user/:id': User
  70. },
  71. container: 'app'
  72. });
  73. // 触发路由
  74. router.push('/user/123');

四、优化与扩展方向

  1. 路由守卫:添加beforeEachafterEach钩子实现权限控制
  2. 懒加载:结合动态import()实现组件按需加载
  3. 嵌套路由:支持多级路由结构
  4. 哈希模式兼容性:处理IE等旧浏览器的onhashchange兼容问题
  5. TypeScript支持:添加类型定义提升代码可维护性

五、实际应用建议

  1. 小型项目首选:Hash Router实现简单,适合原型开发或轻量级应用
  2. 结合构建工具:使用Webpack/Vite等工具的HTML5 History API fallback配置
  3. 服务端配合:确保服务端对所有路由返回同一HTML文件
  4. 性能监控:通过performance.navigation检测路由切换耗时

通过手写Hash Router,开发者不仅能深入理解前端路由机制,还能根据项目需求定制化功能。这种实现方式在面试场景中常被用作考察对前端工程化的理解深度,同时也是学习React Router/Vue Router等库的绝佳起点。

相关文章推荐

发表评论