前端路由的 Hash 模式与 History 模式详解:从原理到实战

605 阅读4分钟

在现代前端开发中,单页应用(SPA) 已成为主流。这类应用通过 前端路由 实现页面切换,而无需每次都向后端请求完整的 HTML 页面。 前端路由的核心在于:根据 URL 的变化动态渲染不同的内容,但不刷新整个页面

在实现方式上,最常用的两种模式是:

  • hash 模式(基于 window.location.hashhashchange 事件)
  • history 模式(基于 HTML5 提供的 pushState()replaceState()popstate 事件)

本文将从零开始讲解这两种模式的工作原理、具体用法,并提供完整的示例代码,帮助你真正掌握如何使用它们来构建一个简单的前端路由系统。


一、Hash 模式

1. 原理简介

URL 中 # 号后面的内容被称为 hash,例如:

https://www.aaa.com/#/home

浏览器对 hash 的处理有以下特性:

  • 修改 location.hash 不会触发页面刷新。
  • 当 hash 发生变化时,会触发 hashchange 事件。
  • hash 不会被发送到服务器。

这些特性非常适合用于前端路由。


2. 核心方法和事件

方法/事件描述
window.location.hash获取或设置当前 URL 的 hash 部分
window.addEventListener('hashchange', callback)监听 hash 的变化

3. 示例:手动实现一个 Hash 路由

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Hash 路由示例</title>
</head>
<body>
    <nav>
        <a href="#/home">首页</a>
        <a href="#/about">关于</a>
        <a href="#/contact">联系</a>
    </nav>

    <div id="app"></div>

    <script>
        // 定义路由表
        const routes = {
            '/home': '<h2>这是首页</h2><p>欢迎来到主页。</p>',
            '/about': '<h2>这是关于页面</h2><p>了解更多关于我们。</p>',
            '/contact': '<h2>这是联系页面</h2><p>请联系我们获取更多信息。</p>'
        };

        // 渲染函数
        function render() {
            const path = window.location.hash.slice(1) || '/home'; // 去除 #
            const content = routes[path] || '<h2>404 页面未找到</h2>';
            document.getElementById('app').innerHTML = content;
        }

        // 初始加载
        window.addEventListener('DOMContentLoaded', render);
        // hash 变化时重新渲染
        window.addEventListener('hashchange', render);
    </script>
</body>
</html>

4. 优缺点

  • 优点

    • 简单易用,兼容性好(支持 IE8+)。
    • 不需要服务器配置。
    • 不会触发页面刷新。
  • 缺点

    • URL 中带有 #,不够美观。
    • 对 SEO 不友好。
    • 不符合传统 URL 风格。

二、History 模式

1. 原理简介

HTML5 提供了新的 API 来操作浏览器的历史记录栈,使得我们可以在不刷新页面的前提下改变 URL。

主要依赖的方法有:

  • history.pushState(state, title, url):向历史记录栈添加一条新记录。
  • history.replaceState(state, title, url):替换当前历史记录。
  • popstate 事件:当用户点击“前进”或“后退”按钮时触发。

2. 核心方法和事件

方法/事件描述
history.pushState(state, title, url)添加一条新的历史记录
history.replaceState(state, title, url)替换当前历史记录
window.addEventListener('popstate', callback)监听浏览器前进/后退动作

3. 示例:手动实现一个 History 路由

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>History 路由示例</title>
</head>
<body>
    <button onclick="navigate('/home')">首页</button>
    <button onclick="navigate('/about')">关于</button>
    <button onclick="navigate('/contact')">联系</button>

    <div id="app">当前视图</div>

    <script>
        // 路由映射表
        const routes = {
            '/home': '<h2>首页</h2><p>欢迎来到主页。</p>',
            '/about': '<h2>关于</h2><p>了解更多关于我们。</p>',
            '/contact': '<h2>联系</h2><p>请联系我们获取更多信息。</p>'
        };

        // 导航函数
        function navigate(path) {
            history.pushState({ path }, '', path); // 添加到历史栈
            render(path);
        }

        // 替换当前路径(不会新增历史记录)
        function replace(path) {
            history.replaceState({ path }, '', path);
            render(path);
        }

        // 渲染函数
        function render(path) {
            const content = routes[path] || '<h2>404 页面未找到</h2>';
            document.getElementById('app').innerHTML = content;
        }

        // 初始加载
        window.addEventListener('DOMContentLoaded', () => {
            const path = location.pathname;
            render(path);
        });

        // 监听 popstate 事件(前进/后退)
        window.addEventListener('popstate', (event) => {
            const path = event.state?.path || location.pathname;
            render(path);
        });
    </script>
</body>
</html>

4. 服务器配置要求

使用 History 模式时,如果用户直接访问 /about 这样的路径,浏览器会向服务器请求该路径下的资源,而服务器如果没有正确配置,就会返回 404。

因此必须确保服务器将所有请求都重定向到你的入口文件(如 index.html)。

示例:Nginx 配置

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

Apache 配置

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.html [L]
</IfModule>

5. 优缺点

  • 优点

    • URL 更加美观,没有 #
    • 更利于 SEO。
    • 支持更复杂的路由逻辑。
  • 缺点

    • 需要服务器配合配置。
    • 在某些老旧浏览器上可能不兼容(但大多数项目已不再考虑)。

三、Hash 模式 vs History 模式对比总结

对比项Hash 模式History 模式
URL 形式#,如 /#/home标准路径,如 /home
是否需服务器配置
兼容性极好(IE8+)需 HTML5 支持(IE10+)
SEO 支持较差更好
推荐场景快速原型、内部工具正式上线、SEO 友好项目

四、实际开发中的选择建议

✅ 使用 Hash 模式的推荐场景:

  • 开发快速原型或演示页面。
  • 不关心 SEO。
  • 不想配置服务器。
  • 需要兼容老旧浏览器。

✅ 使用 History 模式的推荐场景:

  • 面向公众的产品级应用。
  • 需要良好的 SEO 支持。
  • 希望 URL 更加美观。
  • 有能力进行服务器配置(如 Nginx、Apache、Node.js 等)。

五、React 中的使用示例(react-router-dom)

如果你使用 React,可以使用 react-router-dom 来简化路由管理。

安装

npm install react-router-dom

使用示例

import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

function Home() {
  return <h2>首页</h2>;
}

function About() {
  return <h2>关于</h2>;
}

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">首页</Link> | 
        <Link to="/about">关于</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

export default App;

默认情况下,BrowserRouter 使用的是 History 模式。如果你想使用 Hash 模式,可以改用 HashRouter

import { HashRouter as Router } from 'react-router-dom';