【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!

还在分不清什么是单页应用(SPA)?

前端路由、pushStatehashchange 这些词把你绕晕了?

今天,我就用小白能听懂的大白话,带你一口气搞定 SPA + 前端路由这套祖传组合拳!


一、先把话挑明:SPA 是啥?

SPA,全称 Single Page Application ,中文直译就是:单页应用

单页?顾名思义:

整个网站其实就一个页面,一个 index.html,剩下所有的"页面切换",全靠 JavaScript 在里面翻魔术,局部更新。

这可不是嘴上说说,真的是只有一个 HTML 文件:

  • 没有 home.htmlabout.htmlprofile.html 这种文件存在。
  • 有的只是一个大大的 <div id="root"></div>
  • 页面级别内容都靠 React/Vue 这种框架,在这一个 div 里动态塞来塞去。

听起来离谱?可就是因为这样,SPA 才能:

  • 首屏加载后不卡:只要首屏资源加载完,后面切换页面不需要重新向服务器请求 HTML。
  • 体验顺滑:点菜单 URL 变了,但页面没刷新,也不会白屏。
  • 写起来省心 :前端掌控一切页面逻辑,服务器只需要丢个 index.html 给你就行。

二、那多个页面是怎么"假装"出来的?

只靠一个 HTML,怎么假装有多个页面?

核心秘诀:前端路由 + 组件化

  1. 你会写很多页面级别的 React 组件,比如:

    js 复制代码
    function Home() { return <h1>这是首页</h1> }
    function About() { return <h1>关于我们</h1> }
    function Dashboard() { return <h1>控制台</h1> }

    这些就是你假装的"页面"。

  2. 然后你用 <Routes> + <Route> 做个路由表:

    jsx 复制代码
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/dashboard" element={<Dashboard />} />
    </Routes>

    React Router 就帮你看着当前 URL,把匹配到的页面组件挂到 <Routes> 这个"占位符"里。

  3. 当你点击菜单(用 <Link>),或者在代码里手动跳转(useNavigate),React Router 会用 history.pushState 把地址栏 URL 改掉,但页面不刷新,只会把对应组件挂到占位符上。

看起来好像真的在多个页面之间跳来跳去,实际上只是:

  • URL 变了
  • 浏览器没刷
  • React 把 <div id="root"> 里的内容换了

三、URL 怎么能变而页面不刷新?魔法在这里!

先说大白话:

浏览器的地址栏一旦变了,按理说要向服务器重新请求资源,页面就会刷新。

但 SPA 偏偏就能做到:URL 变了,页面照样稳如老狗。

秘诀有俩:

  1. #(hash)
  2. HTML5 的 history.pushState

1)祖传做法:hash + hashchange

还记得很老的网站里有"回到顶部"吗?点一下 URL 变成 http://xxx.com/#top,页面只滚动,没有刷新。

这个 # 后面叫 hash(或 fragment),浏览器遇到 hash:

  • 不会向服务器重新发请求
  • 只是前端自己拿到 location.hash 去解析

聪明的前端就想了:

既然 hash 不会刷新页面,那干脆把路由信息塞 hash 里得了!

于是你会看到:

bash 复制代码
http://example.com/#/
http://example.com/#/about

前端再配个监听器:

js 复制代码
window.addEventListener('hashchange', () => {
  console.log('哈,hash 变了!赶紧切换组件');
});

这样,点击 <a href="#/about">

  • 浏览器地址栏 hash 变了
  • 页面没刷新
  • hashchange 事件触发,前端拿到新的 hash,切换对应的组件

就这样,最早的 SPA 前端路由就有了雏形。


2)现代做法:pushState + popstate

后来 HTML5 出了 history.pushState,前端又找到了新玩具。

用它可以:

js 复制代码
history.pushState({}, '', '/about');
  • 地址栏直接从 / 改成 /about
  • 没有 #,URL 干净利索,看起来跟真实多页站点一模一样。
  • 浏览器照样不刷新页面。

不过有个小坑:

  • pushState 只负责改地址栏,不会自动通知前端"喂,你该换组件了"。

  • 所以框架自己要做监听,或者点后退/前进时靠 popstate 事件感知:

    js 复制代码
    window.addEventListener('popstate', () => {
      console.log('用户点了返回,赶紧切换组件');
    });

3)pushState vs hash

hash pushState
URL 样式 有 #,丑 没 #,干净
会不会刷新 不会 不会
需要服务器配置 不需要 需要后端把所有路径都返回 index.html,否则直接访问 /about 会 404
SEO 友好 不好 好(可以和 SSR 搭配)

所以:

  • 小项目、纯静态托管:hash 路由省心省力。
  • 正式网站、想搞 SEO:pushState 路由香。

四、React Router 里这套是怎么拼起来的?

来,快速回顾一遍你真正在写的东西:

jsx 复制代码
import { BrowserRouter as Router, Routes, Route, Link, useNavigate } from 'react-router-dom';

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>
  );
}

function Home() {
  const navigate = useNavigate();
  return (
    <>
      <h1>这是首页</h1>
      <button onClick={() => navigate('/about')}>点我去关于</button>
    </>
  );
}

function About() {
  return <h1>这是关于页</h1>;
}

这里:


1️⃣ <Router> --- 开启前端路由的开关

它是啥?

<Router>(最常见的是 <BrowserRouter>)是 React Router 的根组件。

可以理解成:

"从这里往下,我要开始用前端路由了,麻烦你把 URL、路由表、页面渲染全帮我管理起来!"

<Router>,React Router 整个就不工作:

  • <Link> 不会拦截 <a> 的点击行为
  • <Routes> 也不会匹配 URL
  • useNavigate 也会失效(因为找不到上下文)

内部到底干了啥?

<BrowserRouter> 为例:

  • 它内部会用到浏览器原生的 HTML5 History API

    • history.pushState:JS 改 URL 不刷新
    • window.onpopstate:监听浏览器的前进/后退按钮
  • 它会把 当前 URL 和一个上下文对象(用 React Context)传给子组件用:

    • <Routes><Link>useNavigate 全都从这个上下文里拿数据。

你需要记住一句话:

<BrowserRouter> 是你前端路由的大脑和中枢,没它啥都跑不起来。


2️⃣ <Link> --- 代替 <a>,劫持点击

它是啥?

在 React Router 里,<Link> 就是用来替代 <a> 的。

写法长得像 <a>

js 复制代码
<Link to="/about">关于我们</Link>

但内部不是 <a> 原生跳转,而是:

  • 阻止 <a> 的默认刷新行为(event.preventDefault()
  • history.pushState 改 URL
  • 告诉路由系统:"嘿,URL 变了,麻烦重新匹配 <Route>。"

为什么要这么做?

因为如果你写:

js 复制代码
<a href="/about">关于我们</a>

浏览器会真跳到 /about,这会向服务器重新发请求。

而 SPA 不需要新请求,只需要前端自己换内容就行,所以 <Link> 的核心目标就是:

点一下,只改 URL,不刷新页面!


内部原理:

  • <Link> 本质就是一个 <a> 元素,只是点击时 JS 劫持了点击。
  • 调用的就是 history.pushState(或者 history.replaceState,如果用 <Link replace>)。

3️⃣ <Routes> & <Route> --- 路由表 + 占位符

<Routes> 是啥?

它相当于:

"我在页面里留个位置,这里会根据当前 URL 渲染对应的页面组件。"

没有 <Routes>,React Router 就不知道要把你 <Route> 定义的页面挂到哪。


<Route> 是啥?

<Route> 就是一条路由规则:

  • path:匹配的 URL
  • element:要挂哪个组件

例如:

jsx 复制代码
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
</Routes>
  • 如果当前 URL 是 /,就渲染 <Home />
  • 如果是 /about,就渲染 <About />

内部原理:

  • <Routes> 会读取当前 URL(来自 <Router> 提供的上下文)。
  • 遍历所有 <Route>,找 path 匹配的那一个。
  • 把对应的 element(页面组件)渲染出来,挂到 <Routes> 所在的位置。

所以你可以把 <Routes> 理解成:

"我在页面里占个位,谁匹配就谁上!"


4️⃣ useNavigate --- JS 里手动跳转的万能钥匙

它是啥?

useNavigate 是一个 React Router 提供的 Hook,用来在 JS 逻辑里做路由跳转。

如果 <Link> 是 HTML 标签里的跳转,useNavigate 就是 JS 里条件跳转:

js 复制代码
const navigate = useNavigate();

function handleLoginSuccess() {
  // 登录成功后,跳转到首页
  navigate('/');
}

内部干了啥?

navigate('/about') 就等于:

  • 调用 history.pushState 改 URL
  • 触发路由系统重新匹配 <Route>

还能干嘛?

navigate 还能:

  • navigate(-1) --- 返回上一页
  • navigate('/about', { replace: true }) --- 用 replaceState,替换当前历史记录,不新增一条
  • navigate('/about', { state: { foo: 'bar' } }) --- 可以带点状态

这四个是怎么串起来的?

我来给你梳理一下整个流程:

xml 复制代码
1. 你写 <BrowserRouter> 把前端路由系统包起来,负责提供路由上下文。
2. 你用 <Link to="/about"> 拦截点击,内部用 pushState 改 URL。
3. URL 改了后,<Routes> 会检测当前 URL,找匹配的 <Route>,渲染对应组件。
4. 如果你需要 JS 里跳转(比如登录成功后自动跳转),就用 useNavigate 调用 pushState,一样走路由匹配。

✅ 核心记忆口诀

名字 干啥用 背后本质
<Router> 开启路由大脑 提供 URL、上下文、监听 popstate
<Link> 点一下改 URL,不刷新 pushState
<Routes> 根据 URL 渲染页面 路由表匹配
useNavigate JS 里手动跳转 pushState or replaceState

这四个搞懂,你就能明白:

React Router = 前端路由的交通枢纽,URL 变了,页面只改局部,刷新?不存在的!


所以这套:

  • 不刷新页面
  • URL 在变
  • 哪个页面显示由路由表决定

五、回到最初的问题:为什么这套能做到"看起来是多页,实际上是单页"?

总结核心点:

  1. 物理上只有一个 HTML。
  2. 逻辑上有很多页面级组件,挂到 <Routes> 里切换。
  3. URL 是状态来源,hash 或 pushState 改变它。
  4. 路由系统匹配 URL,决定挂哪个页面组件。
  5. 页面没刷新,只是 DOM 局部替换了内容。
  6. 对用户来说:没啥区别,就像真在多个页面跳转。

六、最后,这里可能有几个常见小白误区

  • <a><Link> 有啥区别?
    <a> 真跳转,浏览器会整页刷新;<Link> 拦截点击,用 pushState 改 URL,不刷新。
  • pushState 改 URL 为啥不自动换页面?
    因为浏览器没义务管你页面内部渲染啥,路由框架自己得监听。
  • 为什么要服务器配置 fallback?
    因为你访问 example.com/about,如果没有 /about.html,服务器就得返回 index.html,让前端自己解析 /about 路由。

七、总结

单页应用(SPA)的本质就是:

一个页面假装成多个页面,靠前端路由 + URL 状态,做到"看起来多,实际只有一个"。

这套方案看着简单,背后就是:

  • hash 路由的老把式
  • HTML5 的新武器 history.pushState
  • 框架里用 <Routes><Route><Link>useNavigate 串起来

吃透了这套,你就能随时在面试里从容说出:

"其实我们前端路由,就是用 pushState 或 hashchange,拦截点击,动态匹配路由表,局部更新组件,页面不刷新。"

面试官一听,这小子,行。


如果对你有用,别忘了点个赞!有疑问评论区见。

我是小阳,大白话聊前端,我们下次见!

相关推荐
passerby606140 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
yunteng5212 小时前
通用架构(同城双活)(单点接入)
架构·同城双活·单点接入
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax