一、从地址栏说起:为什么需要前端路由
想象你在刷短视频,每次切换视频时页面并没有完全刷新,但地址栏的URL却在变化。这种丝滑体验的背后,就是前端路由在发挥作用。它的核心目标是:在不刷新页面的情况下,通过URL变化来加载不同内容。
早期的网站每次跳转都会导致页面完全刷新,体验差且浪费资源。后来开发者们想出两种解决方案:Hash模式和History模式。下面我们通过代码来理解它们的区别。
(技术栈:JavaScript + HTML)
<!-- Hash模式示例 -->
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="content"></div>
<script>
// 监听hash变化
window.addEventListener('hashchange', () => {
const path = location.hash.slice(1); // 去掉#号
document.getElementById('content').innerHTML = `当前页面:${path}`;
});
</script>
注释说明:
#/home这种带#的URL不会触发页面刷新hashchange事件专门用来监听#后的内容变化
二、Hash模式:简单但不够优雅
Hash模式利用URL中#后面的部分(称为片段标识符)来实现路由。它的特点是:
- 兼容性极好:连IE6都支持
- 无需服务器配置:直接本地打开HTML文件就能用
- URL不够美观:比如
example.com/#/user/profile
但缺点也很明显:
- SEO不友好,搜索引擎可能忽略#后的内容
- 只能修改#后的部分,无法实现真正意义上的路径控制
// Hash模式路由器的极简实现
class HashRouter {
constructor() {
this.routes = {};
window.addEventListener('hashchange', this.load.bind(this));
}
// 添加路由规则
add(path, callback) {
this.routes[path] = callback;
}
// 加载当前路由
load() {
const path = location.hash.slice(1) || '/';
this.routes[path]?.();
}
}
// 使用示例
const router = new HashRouter();
router.add('/home', () => console.log('进入首页'));
router.add('/about', () => console.log('进入关于页'));
三、History API:现代Web应用的标配
HTML5带来了History API,主要包含两个关键方法:
history.pushState():添加历史记录history.replaceState():替换当前历史记录
(技术栈:JavaScript + HTML)
<!-- History模式示例 -->
<a onclick="goTo('/home')">首页</a>
<a onclick="goTo('/about')">关于</a>
<div id="content"></div>
<script>
function goTo(path) {
// 改变URL而不刷新页面
history.pushState(null, '', path);
updateContent(path);
}
// 监听浏览器前进/后退
window.addEventListener('popstate', () => {
updateContent(location.pathname);
});
function updateContent(path) {
document.getElementById('content').innerHTML = `当前页面:${path}`;
}
</script>
关键点说明:
pushState的三个参数分别是状态对象、标题(多数浏览器忽略)和URLpopstate事件只会在浏览器前进后退时触发
四、实战:手写迷你路由库
让我们用History API实现一个完整路由系统:
class HistoryRouter {
constructor() {
this.routes = {};
this.init();
}
init() {
// 初始加载
window.addEventListener('load', () => this.handleRoute());
// 监听前进后退
window.addEventListener('popstate', () => this.handleRoute());
}
// 注册路由
route(path, callback) {
this.routes[path] = callback;
}
// 跳转方法
go(path) {
history.pushState({}, '', path);
this.handleRoute();
}
// 执行路由回调
handleRoute() {
const path = location.pathname;
this.routes[path]?.();
}
}
// 使用示例
const router = new HistoryRouter();
router.route('/home', () => console.log('渲染首页组件'));
router.route('/about', () => console.log('渲染关于页组件'));
// 程序化导航
document.getElementById('btn').addEventListener('click', () => {
router.go('/about');
});
五、关键问题与解决方案
1. 服务端配置
History模式需要服务器支持,否则刷新页面会404。以Nginx为例:
location / {
try_files $uri $uri/ /index.html;
}
这段配置的意思是:先尝试找真实文件,找不到就返回index.html
2. 两种模式如何选择
- 用Hash模式如果:
- 需要兼容老旧浏览器
- 没有服务端控制权
- 用History模式如果:
- 需要干净的URL
- 能做服务端配置
3. 动态路由匹配
现代框架都支持动态路径参数,比如:
router.route('/user/:id', (params) => {
console.log(`用户ID: ${params.id}`);
});
六、现代框架中的路由实现
以Vue Router为例,它同时支持两种模式:
const router = new VueRouter({
mode: 'history', // 或'hash'
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
React的React Router也类似:
<BrowserRouter> {/* History模式 */}
<Route path="/" exact component={Home} />
</BrowserRouter>
<HashRouter> {/* Hash模式 */}
<Route path="/" component={Home} />
</HashRouter>
七、总结与最佳实践
- Hash模式简单粗暴但不够专业,适合快速原型开发
- History模式是生产环境首选,但需要服务端配合
- 现代SPA框架都内置路由解决方案,不建议重复造轮子
- 重要原则:保持URL与视图同步,合理使用导航守卫
最后记住:路由系统的本质是URL与UI的映射关系管理器,理解这点就能举一反三。
评论