实现一个 Hash 路由
手写实现基于 hash 的前端路由系统,支持路由注册、跳转、监听等功能
问题
在单页应用(SPA)中,需要实现一个基于 URL hash 的路由系统。该系统需要支持:
- 路由注册和匹配
- 监听 hash 变化并触发对应的回调
- 编程式导航(跳转到指定路由)
- 支持路由参数解析
解答
class HashRouter {
constructor() {
// 存储路由路径和对应的回调函数
this.routes = {};
// 当前路由
this.currentUrl = '';
// 初始化监听
this.init();
}
// 注册路由
route(path, callback) {
this.routes[path] = callback;
}
// 刷新路由,执行对应的回调
refresh() {
// 获取当前 hash 值,去掉 # 号
this.currentUrl = location.hash.slice(1) || '/';
// 解析路由参数
const [path, query] = this.currentUrl.split('?');
const params = this.parseQuery(query);
// 查找匹配的路由
const matchedRoute = this.matchRoute(path);
if (matchedRoute) {
// 执行对应的回调函数
matchedRoute.callback(params);
}
}
// 匹配路由(支持动态路由)
matchRoute(path) {
// 精确匹配
if (this.routes[path]) {
return { callback: this.routes[path], params: {} };
}
// 动态路由匹配,如 /user/:id
for (let route in this.routes) {
const routeRegex = this.pathToRegex(route);
const match = path.match(routeRegex);
if (match) {
const params = this.extractParams(route, path);
return {
callback: (queryParams) => this.routes[route]({ ...params, ...queryParams }),
params
};
}
}
return null;
}
// 将路由路径转换为正则表达式
pathToRegex(path) {
// 将 /user/:id 转换为 /user/([^/]+)
const pattern = path
.replace(/\//g, '\\/')
.replace(/:\w+/g, '([^\\/]+)');
return new RegExp(`^${pattern}$`);
}
// 提取动态路由参数
extractParams(route, path) {
const params = {};
const routeParts = route.split('/');
const pathParts = path.split('/');
routeParts.forEach((part, index) => {
if (part.startsWith(':')) {
const paramName = part.slice(1);
params[paramName] = pathParts[index];
}
});
return params;
}
// 解析查询参数
parseQuery(query) {
if (!query) return {};
const params = {};
query.split('&').forEach(param => {
const [key, value] = param.split('=');
params[key] = decodeURIComponent(value || '');
});
return params;
}
// 编程式导航
push(path) {
location.hash = path;
}
// 替换当前路由(不产生历史记录)
replace(path) {
location.replace(`${location.pathname}${location.search}#${path}`);
}
// 后退
back() {
history.back();
}
// 前进
forward() {
history.forward();
}
// 初始化监听
init() {
// 监听 hash 变化
window.addEventListener('hashchange', () => {
this.refresh();
});
// 监听页面加载
window.addEventListener('load', () => {
this.refresh();
});
}
}
使用示例
// 创建路由实例
const router = new HashRouter();
// 注册路由
router.route('/', () => {
console.log('首页');
document.getElementById('app').innerHTML = '<h1>首页</h1>';
});
router.route('/about', () => {
console.log('关于页面');
document.getElementById('app').innerHTML = '<h1>关于我们</h1>';
});
// 动态路由
router.route('/user/:id', (params) => {
console.log('用户详情页', params);
document.getElementById('app').innerHTML =
`<h1>用户ID: ${params.id}</h1>`;
});
// 带查询参数的路由
router.route('/search', (params) => {
console.log('搜索页面', params);
document.getElementById('app').innerHTML =
`<h1>搜索关键词: ${params.keyword || '无'}</h1>`;
});
// HTML 中使用
// <a href="#/">首页</a>
// <a href="#/about">关于</a>
// <a href="#/user/123">用户123</a>
// <a href="#/search?keyword=vue">搜索</a>
// 编程式导航
router.push('/about');
router.push('/user/456');
router.push('/search?keyword=react');
// 路由操作
router.back(); // 后退
router.forward(); // 前进
router.replace('/'); // 替换当前路由
关键点
-
原理:利用 URL 的 hash 部分(
#后面的内容)来实现路由,hash 变化不会导致页面刷新 -
事件监听:通过监听
hashchange事件来捕获路由变化,通过load事件处理页面首次加载 -
路由匹配:支持静态路由精确匹配和动态路由参数匹配(如
/user/:id) -
参数解析:
- 动态路由参数:从路径中提取(如
:id) - 查询参数:从
?后面解析(如?keyword=vue)
- 动态路由参数:从路径中提取(如
-
编程式导航:提供
push、replace、back、forward等方法,方便在代码中控制路由跳转 -
正则匹配:将路由路径转换为正则表达式,实现灵活的路由匹配规则
-
兼容性好:hash 路由兼容性强,支持所有浏览器,不需要服务器配置
目录