【适合小白篇】什么是 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,拦截点击,动态匹配路由表,局部更新组件,页面不刷新。"

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


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

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

相关推荐
前端小趴菜0514 分钟前
React - createPortal
前端·vue.js·react.js
晓131338 分钟前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
菜包eo1 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
烛阴1 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
chao_7892 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼3 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原3 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf3 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵4 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
阳火锅4 小时前
Vue 开发者的外挂工具:配置一个 JSON,自动造出一整套页面!
javascript·vue.js·面试