在单页面应用中实现路由功能可以避免页面刷新,提供更流畅的用户体验。下面我将介绍一种模块化的实现方式,可以避免代码重复。
class Router {
constructor(routes) {
this.routes = routes || [];
this.currentRoute = null;
this.root = '/';
// 监听路由变化
window.addEventListener('popstate', () => this.handleRouteChange());
document.addEventListener('DOMContentLoaded', () => this.handleRouteChange());
}
// 添加路由
addRoute(path, callback) {
this.routes.push({ path, callback });
return this;
}
// 路由变化处理
handleRouteChange() {
const path = window.location.pathname.replace(this.root, '') || '/';
const matchedRoute = this.matchRoute(path);
if (matchedRoute) {
this.currentRoute = matchedRoute;
matchedRoute.callback(matchedRoute.params);
} else {
// 默认路由或404处理
const defaultRoute = this.routes.find(r => r.path === '*');
if (defaultRoute) defaultRoute.callback();
}
}
// 路由匹配
matchRoute(path) {
for (const route of this.routes) {
if (route.path === '*') continue;
const paramNames = [];
const regexPath = route.path.replace(/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return '([^/]+)';
});
const match = path.match(new RegExp(`^${regexPath}$`));
if (match) {
const params = {};
paramNames.forEach((name, index) => {
params[name] = match[index + 1];
});
return { ...route, params };
}
}
return null;
}
// 导航到新路由
navigate(path) {
window.history.pushState({}, '', this.root + path);
this.handleRouteChange();
}
// 设置根路径
setRoot(root) {
this.root = root;
return this;
}
}
// 初始化路由
const router = new Router()
.setRoot('/app') // 设置基础路径
.addRoute('/', () => {
// 首页逻辑
document.getElementById('content').innerHTML = '<h1>Home Page</h1>';
})
.addRoute('/about', () => {
// 关于页面逻辑
document.getElementById('content').innerHTML = '<h1>About Us</h1>';
})
.addRoute('/user/:id', (params) => {
// 用户详情页
document.getElementById('content').innerHTML = `<h1>User Profile: ${params.id}</h1>`;
})
.addRoute('*', () => {
// 404处理
document.getElementById('content').innerHTML = '<h1>Page Not Found</h1>';
});
// 在HTML中使用
// <a href="/app/about" onclick="router.navigate('/about'); return false;">About</a>
将路由配置单独提取到模块中:
// routes.js
export const routes = [
{
path: '/',
name: 'home',
component: '<h1>Home Page</h1>'
},
{
path: '/about',
name: 'about',
component: '<h1>About Us</h1>'
},
{
path: '/user/:id',
name: 'user',
component: params => `<h1>User Profile: ${params.id}</h1>`
},
{
path: '*',
component: '<h1>Page Not Found</h1>'
}
];
import { routes } from './routes.js';
class EnhancedRouter extends Router {
constructor() {
super(routes);
this.components = {};
}
// 注册组件
registerComponent(name, component) {
this.components[name] = component;
}
// 渲染组件
renderComponent(component, params) {
const contentEl = document.getElementById('content');
if (typeof component === 'function') {
contentEl.innerHTML = component(params);
} else {
contentEl.innerHTML = component;
}
}
// 重写路由处理
handleRouteChange() {
const path = window.location.pathname.replace(this.root, '') || '/';
const matchedRoute = this.matchRoute(path);
if (matchedRoute) {
this.currentRoute = matchedRoute;
if (matchedRoute.name && this.components[matchedRoute.name]) {
this.renderComponent(this.components[matchedRoute.name], matchedRoute.params);
} else {
this.renderComponent(matchedRoute.component, matchedRoute.params);
}
} else {
const defaultRoute = this.routes.find(r => r.path === '*');
if (defaultRoute) this.renderComponent(defaultRoute.component);
}
}
}
// 完整路由实现
class SPARouter {
constructor(options = {}) {
this.routes = options.routes || [];
this.mode = options.mode || 'history';
this.root = options.root || '/';
this.beforeEach = options.beforeEach || null;
this.afterEach = options.afterEach || null;
this.init();
}
init() {
if (this.mode === 'history') {
window.addEventListener('popstate', () => this.handleRoute());
} else {
window.addEventListener('hashchange', () => this.handleRoute());
}
this.handleRoute();
}
getFragment() {
let fragment = '';
if (this.mode === 'history') {
fragment = decodeURI(window.location.pathname);
fragment = fragment.replace(this.root, '') || '/';
} else {
const match = window.location.href.match(/#(.*)$/);
fragment = match ? match[1] : '/';
}
return fragment;
}
matchRoute(fragment) {
for (const route of this.routes) {
if (route.path === '*') continue;
const paramNames = [];
const regexPath = route.path.replace(/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return '([^/]+)';
});
const match = fragment.match(new RegExp(`^${regexPath}$`));
if (match) {
const params = {};
paramNames.forEach((name, index) => {
params[name] = match[index + 1];
});
return { ...route, params };
}
}
return null;
}
async handleRoute() {
const fragment = this.getFragment();
const matchedRoute = this.matchRoute(fragment) || this.routes.find(r => r.path === '*');
if (!matchedRoute) return;
// 路由守卫
if (this.beforeEach) {
const shouldProceed = await this.beforeEach(matchedRoute, this.currentRoute);
if (shouldProceed === false) return;
}
this.currentRoute = matchedRoute;
try {
if (typeof matchedRoute.callback === 'function') {
await matchedRoute.callback(matchedRoute.params);
}
// 路由后置钩子
if (this.afterEach) {
await this.afterEach(matchedRoute);
}
} catch (error) {
console.error('Route execution error:', error);
}
}
navigate(path) {
if (this.mode === 'history') {
window.history.pushState(null, null, this.root + path);
} else {
window.location.href = `${window.location.href.replace(/#(.*)$/, '')}#${path}`;
}
this.handleRoute();
}
replace(path) {
if (this.mode === 'history') {
window.history.replaceState(null, null, this.root + path);
} else {
window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`);
}
this.handleRoute();
}
}
这种实现方式避免了重复代码,提供了良好的扩展性,可以满足大多数单页面应用的路由需求。