如何用JavaScript实现前端路由与单页应用_手把手搭建SPA核心逻辑【教程】

前端实现真正的SPA需用Histor

y API接管URL变化,初始化渲染+监听popstate+服务端fallback,手写Router核心是路由存储、路径匹配与触发渲染,且须妥善管理state状态与滚动行为。

前端路由不是靠 location.href 跳转就能叫“SPA”的——真要实现无刷新切换、浏览器前进后退正常、URL 可收藏,得自己接管 URL 变化并匹配视图。

History API 替代 hashchange 实现干净 URL

现代 SPA 都走 pushState 路由,避免 #。但直接调用 history.pushState() 不会触发页面重载,你得手动监听变化并响应:

  • history.pushState()history.replaceState() 只改 URL 和状态对象,不触发 popstate 事件;只有用户点后退/前进,或代码调用 history.back() 才触发
  • 必须在初始化时读取 location.pathname 渲染首屏,不能只等 popstate
  • 服务端需配置 fallback:所有非静态资源请求都返回 index.html,否则刷新 404

手写一个最小可用的 Router

不用框架也能跑通核心逻辑。关键就三点:存储路由表、匹配路径、触发渲染。下面这个版本去掉错误处理和嵌套路由,但能 work:

class Router {
  constructor() {
    this.routes = new Map();
    this.currentPath = location.pathname;
  }

  add(path, callback) {
    this.routes.set(path, callback);
  }

  navigate(path) {
    history.pushState({ path }, '', path);
    this.currentPath = path;
    this.handleRoute();
  }

  handleRoute() {
    const callback = this.routes.get(this.currentPath) || this.routes.get('*');
    if (callback) callback();
  }

  init() {
    // 初始化渲染
    this.handleRoute();
    // 监听浏览器导航
    window.addEventListener('popstate', (e) => {
      this.currentPath = e.state?.path || location.pathname;
      this.handleRoute();
    });
  }
}

用法示例:

const router = new Router();

router.add('/', () => {
  document.getElementById('app').innerHTML = '

Home

'; }); router.add('/about', () => { document.getElementById('app').innerHTML = '

About

'; }); router.add('*', () => { document.getElementById('app').innerHTML = '

404

'; }); router.init();

pushState 的 state 参数容易被忽略,但它决定后退时能否还原页面状态

很多人传个空对象甚至 null,结果后退时页面丢了滚动位置、表单输入、筛选条件。正确做法是把当前可序列化的 UI 状态塞进去:

  • state 对象会被序列化进浏览器历史栈,大小限制约 640KB(Chrome),别塞 DOM 节点或函数
  • 比如搜索页跳转到详情页,应在 pushState({ query: 'js', scrollY: 240 }, ...) 中保存关键字段
  • popstate 回调里取 e.state.query 重新发起请求或恢复 UI,而不是重新 load 整个页面

真实项目中绕不开的两个坑

一是 scrollRestoration 默认为 auto,导致后退时自动滚回顶部,掩盖了你本该手动控制的行为;二是 beforeunload 和路由离开守卫不是一回事——beforeunload 是全局离开页面,而路由守卫得你自己在 navigate() 前加判断逻辑,比如表单未保存时弹确认框。

这些细节不处理,用户一刷新、一点后退,体验就断了。路由不是“写完就能跑”,而是“写完才刚开始调”。