一、什么是 SPA
1. 概念
SPA(Single-Page Application,单页应用)是一种前端应用模式。它的特点是:
- 页面始终只加载一次,不会像传统网页那样频繁跳转。
- 局部更新内容,通过 JS 动态修改页面内容,而不是重新刷新整个页面。
👉 类比:
SPA 就像一个杯子,里面的饮料可以是牛奶、茶或者水,但杯子本身始终是同一个,不会换掉。
2. 原理
- 首次加载:获取必要的 HTML、CSS、JS。
- 路由驱动:监听 URL(hash 或 history 模式)变化,动态切换页面内容。
- 前端渲染:由 JS 控制页面局部更新。
3. 框架代表
React、Vue、Angular、Ember 等流行前端框架,基本都是 SPA 模式。
二、SPA vs MPA
1. 概念
- SPA:一个主页面,切换时只更新局部内容。
- MPA:多个独立页面,每次跳转都要重新请求服务器。
2. 对比表
特性 | 单页应用(SPA) | 多页应用(MPA) |
---|---|---|
页面结构 | 一个主页面 + 多个片段 | 每个功能都是独立页面 |
刷新方式 | 局部刷新 | 整页刷新 |
URL 模式 | 哈希 / History API | 传统 URL 路径 |
SEO | 难实现(需 SSR/静态化优化) | 容易实现 |
数据传递 | 内存/状态管理方便 | 依赖 URL、cookie、localStorage |
页面切换速度 | 快(体验好) | 慢(要加载新资源) |
维护成本 | 相对低 | 相对高 |
👉 直观理解:
- SPA = 像手机 App,一直在一个容器里切换界面。
- MPA = 像旧式网站,每次点击都要重新加载整个页面。
三、实现一个 SPA 路由
实现 SPA 的核心:监听 URL 的变化,然后动态渲染内容。
1. Hash 模式
利用 #
后面的内容来表示路由。
kotlin
// 定义 Router
class Router {
constructor () {
this.routes = {}; // 存放路径和回调函数
this.currentUrl = '';
// 监听页面加载 & hash 改变事件
window.addEventListener('load', this.refresh.bind(this), false);
window.addEventListener('hashchange', this.refresh.bind(this), false);
}
// 注册路由
route(path, callback){
this.routes[path] = callback;
}
// 当地址栏 hash 变化时调用
refresh(){
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl] && this.routes[this.currentUrl]();
}
// 主动跳转
push(path) {
location.hash = path;
}
}
// 使用
const router = new Router();
router.route('/', () => console.log('page1'));
router.route('/page2', () => console.log('page2'));
router.push('/'); // 输出 page1
router.push('/page2'); // 输出 page2
🔎 注释:
location.hash
用于获取#xxx
。- 通过
hashchange
事件实现无刷新切换。
2. History 模式
借助 HTML5 history API
来实现,URL 更"干净"。
javascript
// 定义 Router
class Router {
constructor () {
this.routes = {};
this.listenPopState();
}
// 初始化
init(path) {
history.replaceState({path}, null, path);
this.routes[path] && this.routes[path]();
}
// 注册路由
route(path, callback){
this.routes[path] = callback;
}
// 跳转
push(path) {
history.pushState({path}, null, path);
this.routes[path] && this.routes[path]();
}
// 监听浏览器前进/后退
listenPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}
// 使用
const router = new Router();
router.route('/', ()=> console.log('page1'));
router.route('/page2', ()=> console.log('page2'));
router.push('/page2'); // 输出 page2
🔎 注释:
pushState
:添加历史记录。replaceState
:替换当前记录。popstate
:监听用户点击浏览器前进/后退。
👉 区别:Hash 模式兼容性更好,History 模式更优雅但需要服务端支持。
四、SPA 的 SEO 解决方案
因为 SPA 默认是前端渲染的,搜索引擎很难抓到完整内容,需要特殊处理。
1. SSR(服务端渲染)
- 在服务器端渲染页面,再返回 HTML。
- 代表框架:Next.js、Nuxt.js。
2. 静态化
- 预先生成静态 HTML 文件,直接交给服务器。
- 典型做法:静态站点生成(如 VuePress、VitePress)。
3. 爬虫专用渲染
- 判断请求来源是爬虫时,用 PhantomJS / Puppeteer 渲染完整 HTML,再返回给爬虫。
五、拓展思考
- 混合应用模式
很多实际项目并不是纯 SPA,而是 SPA + 部分 MPA,例如后台管理系统用 SPA,官网首页用 MPA。 - 前后端分离
SPA 强调前端控制路由,后端只提供数据接口(API)。 - 性能优化
- 使用懒加载(按需加载组件)。
- 预渲染关键页面。
- 使用骨架屏改善体验。
六、潜在问题
- SEO 不友好 → 需 SSR 或预渲染。
- 首屏加载慢 → 因为首次要加载较多 JS。
- 浏览器兼容性 → history 模式需服务端配置 fallback。
- 内存管理 → 组件未卸载可能导致内存泄漏。
✅ 总结一句话:
SPA 提升了用户体验和开发效率,但也带来了 SEO 和首屏加载的挑战,合理使用 SSR、懒加载和预渲染,才能发挥最大价值。
本文部分内容借助 AI 辅助生成,并由作者整理审核。