Skip to main content

深入理解 SPA 单页面应用

一、什么是 SPA

SPA(Single-Page application)单页面应用通过动态重写当前页面来与用户交互,避免了页面切换时的重新加载,从而提升用户体验,SPA 在页面初始化时加载必要代码,之后不需要重新加载,常见的 React、Vue、Angular 都属于 SPA。

优点:

  • 具有桌面应用的即时性、网站的可移植性和可访问性;
  • 用户体验好,内容改变时不需要重新加载整个页面;
  • 良好的前后端分离,分工更明确。

缺点:

  • 不利于搜索引擎的抓取;
  • 首次渲染速度相对较慢。

二、SPA 与 MPA 的区别

SPA(Single-Page application)是单页面应用,而 MPA(MultiPage-Page application)是多页面应用,每个页面都是独立的,访问时都需要重新加载 HTML、CSS、JS 文件,具体区别如下:

单页面应用(SPA)多页面应用(MPA)
组成一个主页面和多个页面片段多个主页面
刷新方式局部刷新整页刷新
url 模式哈希模式历史模式
SEO 搜索引擎优化难实现,可使用 SSR 改善易实现
页面间数据传递容易通过 url、cookie、localStorage 等方式传递
页面切换速度速度快,用户体验良好切换加载资源,速度慢,用户体验差
维护成本相对容易相对复杂

三、SPA 的原理及实现

SPA 的原理就是通过 JS 监听 URL 的变化,对页面进行处理。前端路由主要有 hash 和 history 两种实现方式。

1、hash 模式

hash 模式就是在 url 后加上 # 和指定的字符。例如 www.leophen.top/#/pageA , 这里的 #/pageA 就是 hash。

hash 的改变不会导致浏览器发送请求,但会触发 hashchange ,浏览器的前进、后退也能操作 hash。基于这三点,通过监听 url 中的 hash 来进行路由跳转,实现如下:

hash-router.js
class HashRouter {
// 存放路由
constructor() {
this.routes = {}
}
// 注册路由
registry(path, callback = () => { }) {
this.routes[path] = callback
}
// 更新视图
updateView() {
const hash = location.hash.slice(1) || '/'
this.routes[hash]?.()
}
// 初始化
init() {
window.addEventListener('load', this.updateView.bind(this), false)
window.addEventListener('hashchange', this.updateView.bind(this), false)
}
}

实现效果:

2、history 模式

HTML5 之前主要使用 back、forward、go 等方法实现页面跳转,而 HTML5 之后提供了 history.pushStatehistory.replaceState 来添加或修改历史记录,history 模式就是利用 pushStatereplaceState 来实现路由。

history 模式的核心:

  • history.pushState:在保留现有历史记录的同时,将 url 加入到历史记录中;
  • history.replaceState:将历史记录中的当前页面历史替换为 url。
history-router.js
class HistoryRouter {
// 存放路由
constructor() {
this.routes = {}
}
// 注册路由
registry(path, callback = () => { }) {
this.routes[path] = callback
}
// 更新视图
updateView(path, type) {
if (type === 'init') {
history.replaceState({ path }, null, path);
} else {
history.pushState({ path }, null, path);
}
this.routes[path]?.()
}
// 初始化
init() {
window.addEventListener('load', () => this.updateView('/', 'init'), false)
window.addEventListener('popstate', () => {
this.updateView(window.location.pathname, 'init')
})
}
}

实现效果:

注意 history 模式下刷新页面时会 404,需要后端配合匹配路由。

3、hash 与 history 模式的区别

  • hash 模式的兼容性比 history 模式(基于 HTML5)高;
  • hash 模式会在 url 中夹带 # 符;
  • hash 模式不需要后端配合匹配路由,而 history 模式需要。

五、如何给 SPA 做 SEO

利用 SSR 服务端渲染给 SPA 做 SEO,将组件在服务端直接渲染成 HTML,再返回给浏览器,例如 Next.jsNuxt.js