一、从地址栏说起:为什么需要前端路由

想象你在刷短视频,每次切换视频时页面并没有完全刷新,但地址栏的URL却在变化。这种丝滑体验的背后,就是前端路由在发挥作用。它的核心目标是:在不刷新页面的情况下,通过URL变化来加载不同内容

早期的网站每次跳转都会导致页面完全刷新,体验差且浪费资源。后来开发者们想出两种解决方案:Hash模式History模式。下面我们通过代码来理解它们的区别。

(技术栈:JavaScript + HTML)

<!-- Hash模式示例 -->
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="content"></div>

<script>
  // 监听hash变化
  window.addEventListener('hashchange', () => {
    const path = location.hash.slice(1); // 去掉#号
    document.getElementById('content').innerHTML = `当前页面:${path}`;
  });
</script>

注释说明:

  1. #/home这种带#的URL不会触发页面刷新
  2. hashchange事件专门用来监听#后的内容变化

二、Hash模式:简单但不够优雅

Hash模式利用URL中#后面的部分(称为片段标识符)来实现路由。它的特点是:

  • 兼容性极好:连IE6都支持
  • 无需服务器配置:直接本地打开HTML文件就能用
  • URL不够美观:比如example.com/#/user/profile

但缺点也很明显:

  1. SEO不友好,搜索引擎可能忽略#后的内容
  2. 只能修改#后的部分,无法实现真正意义上的路径控制
// Hash模式路由器的极简实现
class HashRouter {
  constructor() {
    this.routes = {};
    window.addEventListener('hashchange', this.load.bind(this));
  }
  
  // 添加路由规则
  add(path, callback) {
    this.routes[path] = callback;
  }
  
  // 加载当前路由
  load() {
    const path = location.hash.slice(1) || '/';
    this.routes[path]?.();
  }
}

// 使用示例
const router = new HashRouter();
router.add('/home', () => console.log('进入首页'));
router.add('/about', () => console.log('进入关于页'));

三、History API:现代Web应用的标配

HTML5带来了History API,主要包含两个关键方法:

  • history.pushState():添加历史记录
  • history.replaceState():替换当前历史记录

(技术栈:JavaScript + HTML)

<!-- History模式示例 -->
<a onclick="goTo('/home')">首页</a>
<a onclick="goTo('/about')">关于</a>
<div id="content"></div>

<script>
  function goTo(path) {
    // 改变URL而不刷新页面
    history.pushState(null, '', path);
    updateContent(path);
  }
  
  // 监听浏览器前进/后退
  window.addEventListener('popstate', () => {
    updateContent(location.pathname);
  });
  
  function updateContent(path) {
    document.getElementById('content').innerHTML = `当前页面:${path}`;
  }
</script>

关键点说明:

  1. pushState的三个参数分别是状态对象、标题(多数浏览器忽略)和URL
  2. popstate事件只会在浏览器前进后退时触发

四、实战:手写迷你路由库

让我们用History API实现一个完整路由系统:

class HistoryRouter {
  constructor() {
    this.routes = {};
    this.init();
  }
  
  init() {
    // 初始加载
    window.addEventListener('load', () => this.handleRoute());
    // 监听前进后退
    window.addEventListener('popstate', () => this.handleRoute());
  }
  
  // 注册路由
  route(path, callback) {
    this.routes[path] = callback;
  }
  
  // 跳转方法
  go(path) {
    history.pushState({}, '', path);
    this.handleRoute();
  }
  
  // 执行路由回调
  handleRoute() {
    const path = location.pathname;
    this.routes[path]?.();
  }
}

// 使用示例
const router = new HistoryRouter();
router.route('/home', () => console.log('渲染首页组件'));
router.route('/about', () => console.log('渲染关于页组件'));

// 程序化导航
document.getElementById('btn').addEventListener('click', () => {
  router.go('/about');
});

五、关键问题与解决方案

1. 服务端配置

History模式需要服务器支持,否则刷新页面会404。以Nginx为例:

location / {
  try_files $uri $uri/ /index.html;
}

这段配置的意思是:先尝试找真实文件,找不到就返回index.html

2. 两种模式如何选择

  • 用Hash模式如果:
    • 需要兼容老旧浏览器
    • 没有服务端控制权
  • 用History模式如果:
    • 需要干净的URL
    • 能做服务端配置

3. 动态路由匹配

现代框架都支持动态路径参数,比如:

router.route('/user/:id', (params) => {
  console.log(`用户ID: ${params.id}`);
});

六、现代框架中的路由实现

以Vue Router为例,它同时支持两种模式:

const router = new VueRouter({
  mode: 'history', // 或'hash'
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About }
  ]
})

React的React Router也类似:

<BrowserRouter>  {/* History模式 */}
  <Route path="/" exact component={Home} />
</BrowserRouter>

<HashRouter>     {/* Hash模式 */}
  <Route path="/" component={Home} />
</HashRouter>

七、总结与最佳实践

  1. Hash模式简单粗暴但不够专业,适合快速原型开发
  2. History模式是生产环境首选,但需要服务端配合
  3. 现代SPA框架都内置路由解决方案,不建议重复造轮子
  4. 重要原则:保持URL与视图同步,合理使用导航守卫

最后记住:路由系统的本质是URL与UI的映射关系管理器,理解这点就能举一反三。