彻底搞懂前端路由:从 Hash 到 History 的演进与实践

一、什么是路由?

在 Web 开发中,路由(Route) 是 URL 与资源 之间的映射关系。

  • 服务端路由:传统多页应用(MPA)里,浏览器每跳转一次 URL,都会向服务器发起一次新的请求,服务器根据 URL 返回不同的 HTML 页面。
  • 前端路由 :单页应用(SPA)里,浏览器只加载一次 index.html,后续 URL 变化完全在客户端完成 ,不触发整页刷新,通过 JS 动态渲染局部视图仅局部更新 DOM不刷新页面

二、SPA 为什么需要前端路由?

维度 多页应用(MPA) 单页应用(SPA)
URL 与 资源 映射 一个 URL 对应一份 HTML 一个 URL 对应一份组件
页面切换 整页刷新 局部 DOM 替换
体验 白屏、状态丢失,割裂 无缝、沉浸
SEO 天然友好 需 SSR/预渲染

结论前端路由 让 SPA 既拥有"不刷新"的流畅体验,又能在浏览器地址栏正确反映当前视图状态。


三、前端路由要解决的两个核心问题

1. 如何感知 URL 变化?

浏览器原生并不会在 URL 变化时主动通知我们,需要手动监听。

  • Hash 模式监听 hashchange
  • History 模式监听 popstate

2. 如何根据 URL 找到并渲染对应组件?

需要维护一个路由表,路由表中存储了 URL 和组件的映射关系。当 URL 发生变化时,根据路由表找到对应的组件。


四、Hash 模式:最简单的前端路由

4.1 原理

URL 中 # 后面的部分称为 Hash

  • # 及之后的内容不会被发送到服务器 ,因此改变 Hash 不会触发页面刷新
  • 浏览器原生事件 hashchange ,用来实时监听 Hash 变化。

4.2 Hash 路由实现步骤

  1. 定义路由表
js 复制代码
const routes = [
    {
        path: '/home',
        component: () => '<h1>首页页面</h1>'
    },
    {
        path: '/list',
        component: () => '<h1>列表页页面</h1>'
    }
]
  1. 监听 hashchange 事件,当hash值变更时,触发 hashchange 事件,通过 location 获取 # 和后面的内容。在事件处理函数中,根据 hash 值,找到对应组件,将组件渲染到页面中。
js 复制代码
// 监听 URL 的变化
window.addEventListener('hashchange', (e) => {
    renderView(location.hash)
})

function renderView(url){ // #/home
    // findIndex() 方法返回数组中满足提供的第一个元素的索引
    // 找到路由配置中,path 与 url 匹配的项
    const index = routes.findIndex(item => {
        return ('#' + item.path) === url
    })
    // 获取路由出口
    let routerView = document.getElementById('root')
    // 路由出口中,展示路由组件
    routerView.innerHTML = routes[index].component()
}

✅ 优点:实现简单,兼容性好(IE8+)。

❌ 缺点:URL 带 #,不美观;SEO 不友好;服务端无法直接拿到 Hash 部分。


五、History 模式:更优雅的现代方案

5.1 History API 简介

HTML5 新增的 window.history 提供了无刷新操作浏览器历史记录的能力。

方法 作用 是否触发 popstate
pushState(state, title, url) 新增历史记录
replaceState(state, title, url) 替换当前记录
back / forward / go(n) 浏览器或 JS 导航

注意

  • 只有浏览器前进/后退 才会触发 popstate
  • 代码调用 pushState/replaceState 后需手动渲染

5.2 History 路由实现步骤

  1. 定义路由表;
  2. 监听popstate事件,当 URL 发生变化时,触发popstate事件,通过 location 获取 URL 中的路径。
js 复制代码
// popstate 事件监听浏览器前进/后退
window.addEventListener('popstate', () => {
    renderView(location.pathname)
})
  1. 页面加载完成时,调用 onLoad方法,拦截 <a> 标签默认行为。
  • onLoad 方法:找到所有的链接,给每个链接添加点击事件;
  • click 点击事件:阻止默认事件,修改 URL,调用 renderView 方法。
js 复制代码
window.addEventListener('DOMContentLoaded', () => {
    onLoad()
})

function onLoad() {
    // 找到所有的链接
    let linkList = document.querySelectorAll('a[href]')
    // 遍历所有的链接
    linkList.forEach(item => {
        // 给每个链接添加点击事件
        item.addEventListener('click', (e) => {
            // 阻止默认事件
            e.preventDefault()
            // pushState() 进入浏览器的缓存栈,但是不触发 popstate 事件,不受前进后退影响
            history.pushState(null, '', item.getAttribute('href'))
            renderView(item.getAttribute('href'))
        })
    })
}
  1. 定义 renderView 方法渲染对应的组件。
js 复制代码
function renderView(url) {
    let index = routes.findIndex(item => {
        return item.path === url
    })
    routerView.innerHTML = routes[index].component()
}

✅ 优点:URL 干净,支持 SEO,用户体验更好。

❌ 缺点:需要服务端配合(Nginx/Node 配置兜底),IE10+。


六、框架路由源码级对比

特性 Vue Router React Router Angular Router
模式 hash / history / abstract hash / history / memory hash / history
懒加载 () => import() React.lazy loadChildren
守卫/钩子 全局 / 路由 / 组件 自定义 Hooks Guards & Resolvers
嵌套路由 <router-view> <Outlet> <router-outlet>
数据获取 beforeEnter, asyncData loader resolve

七、总结

维度 Hash History
URL 美观度 ❌ 带 # ✅ 干净
浏览器兼容 ✅ IE8+ ⚠️ IE10+
服务端配置 不需要 需要兜底
SEO ✅ 好(可 SSR)
实现复杂度

八、参考链接

相关推荐
花菜会噎住11 分钟前
Vue3核心语法进阶(computed与监听)
前端·javascript·vue.js
I'mxx25 分钟前
【vue(2)插槽】
javascript·vue.js
花菜会噎住34 分钟前
Vue3核心语法基础
前端·javascript·vue.js·前端框架
全宝34 分钟前
echarts5实现地图过渡动画
前端·javascript·echarts
vjmap34 分钟前
MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
前端·人工智能·gis
simple_lau1 小时前
鸿蒙设备如何与低功耗蓝牙设备通讯
前端
啃火龙果的兔子2 小时前
解决 Node.js 托管 React 静态资源的跨域问题
前端·react.js·前端框架
ttyyttemo2 小时前
Compose生命周期---Lifecycle of composables
前端
以身入局2 小时前
FragmentManager 之 addToBackStack 作用
前端·面试
sophie旭2 小时前
《深入浅出react》总结之 10.7 scheduler 异步调度原理
前端·react.js·源码