SPA路由核心是用history.pushState更新URL而不刷新页面,需配合popstate监听、拦截a标签点击、健壮路径匹配及首次加载手动渲染。

直接用 history.pushState 操作 URL 而不刷新页面
单页应用(SPA)路由的核心,是不让浏览器真正跳转,而是手动更新地址栏并响应变化。关键在于绕过默认的页面重载行为:history.pushState 和 history.replaceState 是唯一直接、标准、无兼容性问题的方式(IE10+ 支持)。location.hash 虽然兼容性更好,但会多一个 #,且不利于 SEO 和服务端直出。
实操建议:
- 每次“跳转”都调用
history.pushState({ path: '/user' }, '', '/user')—— 第一个参数是状态对象(可存任意数据),第二个是 title(通常为空),第三个才是真实路径 - 必须配合
popstate事件监听返回/前进操作:window.addEventListener('popstate', e => render(e.state?.path || location.pathname)) - 注意:
pushState不会触发popstate,只对浏览器前进/后退生效;首次加载仍需手动调用一次render
拦截 点击并阻止默认跳转
用户点击链接时,浏览器默认会发起新请求,这会彻底破坏 SPA 体验。必须主动捕获所有内部链接点击,改用 JS 控制路由逻辑。
常见错误现象:点击后页面白屏或整页刷新,说明某些 没被拦截。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 用事件委托绑定到
document,避免漏掉动态插入的链接:document.addEventListener('click', e => { if (e.target.matches('a[href^="/"]') && !e.target.hasAttribute('target')) { e.preventDefault(); router.navigate(e.target.getAttribute('href')); } }) - 只拦截以
/开头、且没target属性的链接(排除外链和新窗口) - 不要用
onclick内联 handler,维护成本高且易遗漏
匹配路径时慎用字符串相等,优先考虑前缀或正则
简单用 location.pathname === '/user' 只能处理静态路径,一旦涉及参数(如 /user/123)、嵌套路由(/admin/users)或带查询参数的场景,就会失效。
实操建议:
- 基础场景可用
pathname.startsWith('/admin')判断布局层级 - 带参数的路径推荐用正则:
const match = pathname.match(/^\/user\/(\d+)$/),提取match[1]即 ID - 避免手写复杂正则,可封装一个轻量
matchPath(pathname, pattern)函数,支持"/user/:id"这类写法(但不必引入完整 React Router 语法) - 注意结尾斜杠:/user 和 /user/ 是不同路径,统一处理(比如强制去除末尾
/)
popstate 事件监听不到初始页面加载
这是最容易忽略的坑:用户直接访问 https://site.com/user 或刷新页面时,popstate 根本不会触发,导致页面空白或渲染首页。
原因在于该事件只在历史栈变化(前进/后退)时抛出,首次加载属于“进入”,不是“切换”。
实操建议:
- 页面初始化时,必须显式读取
location.pathname并调用渲染函数:render(location.pathname) - 把初始化逻辑和
popstate回调抽成同一个函数,避免重复代码 - 如果用了
pushState初始化状态(比如从服务器注入当前路径),也要确保 state 对象正确传入,否则e.state为null











