前端路由的 hash 与 history 模式怎样实现?刷新 404 如何配置?Link 和 a 有什么区别?
先理解原理和示例,再练习完整口述与追问。
回答要点
hash 模式改变 # 后的片段,不请求服务器,监听 hashchange;history 模式用 pushState/replaceState 修改路径,监听 popstate 处理前进后退,但刷新时服务器会收到真实路径,必须回退到 index.html。路由库的 Link 渲染为 a,拦截普通左键点击改为客户端导航,保留 href 以支持新标签打开和抓取。
原理拆解
hash 变化只在页面内导航,服务器看不到 # 之后的内容,部署简单,但 URL 不美观,也不利于服务端渲染。history.pushState(state, '', url) 修改地址栏并新增历史记录,但不会触发 popstate,也不会发请求,路由库需要在调用后自行渲染;用户点击前进后退时才触发 popstate。
刷新 /users/42 时浏览器请求服务器的这个路径,静态服务器找不到文件就返回 404。需要让“找不到的前端路由”回退到 index.html,同时静态资源和 API 的真实 404 不应被吞掉。React Router 和 Vue Router 都封装了 history 对象:createWebHistory、createWebHashHistory、createMemoryHistory 分别对应三种环境。
示例与推演
function createRouter(routes, root) {
function render() {
const route = routes.find(item => item.path === location.pathname) || routes.find(item => item.path === '*')
root.textContent = route ? route.title : '404'
}
document.addEventListener('click', event => {
const link = event.target.closest('a[data-link]')
if (!link || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || link.target) return
event.preventDefault()
history.pushState(null, '', link.href)
render()
})
window.addEventListener('popstate', render)
render()
}
location / { try_files $uri $uri/ /index.html; }
易错点
拦截点击时要放过修饰键、中键和 target="_blank",否则用户无法新开标签;子路径部署需要配置 base。不要把 /api 也回退到 index.html。
参考回答可直接练习口述
hash 模式靠改 # 后面的部分并监听 hashchange,服务器不感知;history 模式用 pushState 改路径,自己触发渲染,前进后退靠 popstate。history 刷新时服务器会收到真实路径,所以要配置回退,例如 Nginx 的 try_files 指向 index.html,但 API 和静态资源不能被回退。Link 本质渲染 a 标签,拦截普通左键点击做客户端跳转,保留 href,所以仍能右键新标签打开,也利于抓取。