工程化与框架系列(16)--前端路由实现
- 其他
- 2025-09-15 08:24:01

前端路由实现 🧭
前端路由是单页应用(SPA)的核心功能,它使得在不刷新页面的情况下实现视图切换和状态管理。本文将深入探讨前端路由的实现原理和关键技术。
前端路由概述 🌐💡 小知识:前端路由是指在单页应用中,通过 JS 动态操作改变页面内容,而不触发浏览器刷新的技术,使得用户体验更加流畅。
为什么需要前端路由在传统的多页面应用中,页面跳转需要向服务器发送请求并重新加载页面。而在单页应用中,前端路由带来以下优势:
提升用户体验
无刷新页面切换更快的响应速度平滑的过渡效果保持页面状态减轻服务器压力
减少HTTP请求按需加载资源降低带宽消耗减轻服务器负载前后端分离
清晰的职责边界独立开发和部署更好的可维护性更灵活的架构 前端路由的实现方式 🔄前端路由主要有两种实现方式:Hash模式和History模式。
Hash模式Hash模式是基于URL的hash(即#后面的部分)实现的,hash值的变化不会触发页面刷新。
// hash-router.ts class HashRouter { private routes: Record<string, Function>; private currentHash: string; constructor() { this.routes = {}; this.currentHash = ''; this.init(); } private init() { // 初始化时监听hashchange事件 window.addEventListener('hashchange', this.refresh.bind(this)); // 初始加载 window.addEventListener('load', this.refresh.bind(this)); } private refresh() { // 获取当前hash值,去除#号 this.currentHash = location.hash.slice(1) || '/'; // 执行对应路由回调 this.routes[this.currentHash] && this.routes[this.currentHash](); } // 注册路由和回调 public route(path: string, callback: Function) { this.routes[path] = callback; } // 导航到指定路由 public navigateTo(path: string) { location.hash = path; } } // 使用示例 const router = new HashRouter(); router.route('/', () => { document.getElementById('app')!.innerHTML = '<h1>首页</h1>'; }); router.route('/about', () => { document.getElementById('app')!.innerHTML = '<h1>关于我们</h1>'; }); router.route('/contact', () => { document.getElementById('app')!.innerHTML = '<h1>联系我们</h1>'; }); // 使用导航 document.getElementById('homeLink')!.addEventListener('click', () => { router.navigateTo('/'); }); History模式History模式基于HTML5的History API,通过pushState和replaceState方法实现URL变化但不刷新页面。
// history-router.ts class HistoryRouter { private routes: Record<string, Function>; private currentPath: string; constructor() { this.routes = {}; this.currentPath = ''; this.init(); } private init() { // 监听popstate事件 window.addEventListener('popstate', this.handlePopState.bind(this)); // 初始加载 window.addEventListener('load', this.refresh.bind(this)); // 拦截所有a标签点击事件 document.addEventListener('click', e => { const target = e.target as HTMLElement; if (target.tagName === 'A') { e.preventDefault(); const href = (target as HTMLAnchorElement).getAttribute('href'); if (href) this.navigateTo(href); } }); } private handlePopState() { this.refresh(); } private refresh() { this.currentPath = location.pathname || '/'; this.routes[this.currentPath] && this.routes[this.currentPath](); } // 注册路由和回调 public route(path: string, callback: Function) { this.routes[path] = callback; } // 导航到指定路由 public navigateTo(path: string) { history.pushState(null, '', path); this.refresh(); } } // 使用示例 const router = new HistoryRouter(); router.route('/', () => { document.getElementById('app')!.innerHTML = '<h1>首页</h1>'; }); router.route('/about', () => { document.getElementById('app')!.innerHTML = '<h1>关于我们</h1>'; }); 两种模式的对比 特性Hash模式History模式URL格式带有#号(example /#/page)干净的URL(example /page)服务器配置不需要特殊配置需要服务器配置支持所有路由返回index.htmlSEO友好性较差,搜索引擎可能忽略#后内容较好,URL格式符合传统网页规范兼容性兼容性好,支持旧版浏览器需要HTML5 History API支持刷新页面保持路由状态需要服务器配置,否则可能404 高级路由实现 🚀接下来,我们将实现一个更加完善的前端路由,支持参数匹配、嵌套路由和路由守卫等高级功能。
路由参数和匹配 // advanced-router.ts interface RouteConfig { path: string; component: any; beforeEnter?: (to: Route, from: Route, next: () => void) => void; children?: RouteConfig[]; } interface Route { path: string; params: Record<string, string>; query: Record<string, string>; } class Router { private routes: RouteConfig[]; private currentRoute: Route | null; private previousRoute: Route | null; private mode: 'hash' | 'history'; constructor(options: { routes: RouteConfig[]; mode?: 'hash' | 'history'; }) { this.routes = options.routes; this.currentRoute = null; this.previousRoute = null; this.mode = options.mode || 'hash'; this.init(); } private init() { if (this.mode === 'hash') { window.addEventListener('hashchange', this.handleRouteChange.bind(this)); window.addEventListener('load', this.handleRouteChange.bind(this)); } else { window.addEventListener('popstate', this.handleRouteChange.bind(this)); window.addEventListener('load', this.handleRouteChange.bind(this)); // 拦截链接点击 document.addEventListener('click', e => { const target = e.target as HTMLElement; if (target.tagName === 'A') { e.preventDefault(); const href = (target as HTMLAnchorElement).getAttribute('href'); if (href) this.navigateTo(href); } }); } } private handleRouteChange() { const path = this.getPath(); const route = this.matchRoute(path); if (route) { this.previousRoute = this.currentRoute; this.currentRoute = route; // 执行路由钩子 this.executeRouteGuards(); } } private getPath(): string { if (this.mode === 'hash') { return location.hash.slice(1) || '/'; } else { return location.pathname || '/'; } } // 解析URL参数 private parseQuery(queryString: string): Record<string, string> { const query: Record<string, string> = {}; const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&'); for (let pair of pairs) { if (pair === '') continue; const parts = pair.split('='); query[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1] || ''); } return query; } private matchRoute(path: string): Route | null { // 分离查询参数 const [pathWithoutQuery, queryString] = path.split('?'); const query = this.parseQuery(queryString || ''); for (let route of this.routes) { const params = this.matchPath(pathWithoutQuery, route.path); if (params !== null) { return { path: route.path, params, query }; } } return null; } // 路径匹配逻辑,支持动态参数 private matchPath(path: string, routePath: string): Record<string, string> | null { // 将路由路径转换为正则表达式 const paramNames: string[] = []; const regexPath = routePath.replace(/:([^/]+)/g, (_, paramName) => { paramNames.push(paramName); return '([^/]+)'; }).replace(/\*/g, '.*'); const match = path.match(new RegExp(`^${regexPath}$`)); if (!match) return null; const params: Record<string, string> = {}; // 从匹配结果中提取参数值 for (let i = 0; i < paramNames.length; i++) { params[paramNames[i]] = match[i + 1]; } return params; } // 执行路由守卫 private executeRouteGuards() { const routeConfig = this.findRouteConfig(this.currentRoute!.path); if (routeConfig && routeConfig.beforeEnter) { routeConfig.beforeEnter( this.currentRoute!, this.previousRoute!, () => this.renderComponent(routeConfig ponent) ); } else { if (routeConfig) this.renderComponent(routeConfig ponent); } } private findRouteConfig(path: string): RouteConfig | null { return this.routes.find(route => route.path === path) || null; } private renderComponent(component: any) { // 实际项目中这里会根据框架不同有不同实现 // 这里简化为直接将组件内容插入到指定容器 document.getElementById('app')!.innerHTML = component.template || ''; } public navigateTo(path: string) { if (this.mode === 'hash') { location.hash = path; } else { history.pushState(null, '', path); this.handleRouteChange(); } } } // 使用示例 const router = new Router({ mode: 'history', routes: [ { path: '/', component: { template: '<h1>首页</h1>' } }, { path: '/user/:id', component: { template: '<h1>用户详情页</h1>' }, beforeEnter: (to, from, next) => { // 路由守卫逻辑 console.log(`从${from.path}导航到${to.path}`); console.log(`用户ID: ${to.params.id}`); next(); } }, { path: '/about', component: { template: '<h1>关于我们</h1>' } } ] }); 嵌套路由实现 // nested-routes.ts interface RouteConfig { path: string; component: any; children?: RouteConfig[]; } class NestedRouter { private routes: RouteConfig[]; private currentPath: string; constructor(routes: RouteConfig[]) { this.routes = routes; this.currentPath = ''; this.init(); } private init() { window.addEventListener('hashchange', this.refresh.bind(this)); window.addEventListener('load', this.refresh.bind(this)); } private refresh() { this.currentPath = location.hash.slice(1) || '/'; this.render(this.routes, this.currentPath); } // 递归渲染嵌套路由 private render(routes: RouteConfig[], path: string, parentPath: string = '') { for (const route of routes) { // 构建完整的路由路径 const fullPath = parentPath + route.path; // 检查当前路径是否匹配该路由 if (path === fullPath || path.startsWith(fullPath + '/')) { // 渲染当前路由组件 this.renderComponent(route ponent, 'router-view'); // 如果有子路由,并且当前路径比当前路由更长,则尝试渲染子路由 if (route.children && path.length > fullPath.length) { this.render(route.children, path, fullPath); } return; } } } private renderComponent(component: any, selector: string) { // 这里是一个简化的实现,实际情况需要根据具体框架调整 const el = document.querySelector(selector); if (el) { el.innerHTML = component.template || ''; } } public navigateTo(path: string) { location.hash = path; } } // 使用示例 const router = new NestedRouter([ { path: '/', component: { template: ` <div> <h1>首页</h1> <div class="router-view"></div> </div> ` }, children: [ { path: '/dashboard', component: { template: '<h2>仪表盘</h2>' } }, { path: '/profile', component: { template: ` <div> <h2>个人资料</h2> <div class="router-view"></div> </div> ` }, children: [ { path: '/profile/info', component: { template: '<h3>基本信息</h3>' } }, { path: '/profile/settings', component: { template: '<h3>账户设置</h3>' } } ] } ] }, { path: '/about', component: { template: '<h1>关于我们</h1>' } } ]); 路由组件实现 🧩 自定义Link组件 // router-components.ts // 自定义Link组件 class RouterLink extends HTMLElement { constructor() { super(); this.addEventListener('click', this.handleClick.bind(this)); } static get observedAttributes() { return ['to']; } connectedCallback() { this.render(); } attributeChangedCallback() { this.render(); } private render() { const to = this.getAttribute('to') || '/'; this.innerHTML = `<a href="${to}">${this.textContent || to}</a>`; } private handleClick(e: Event) { e.preventDefault(); const to = this.getAttribute('to') || '/'; // 触发自定义事件,让Router处理导航 this.dispatchEvent(new CustomEvent('router-navigate', { bubbles: true, detail: { to } })); } } // 注册自定义元素 customElements.define('router-link', RouterLink); // 路由视图组件 class RouterView extends HTMLElement { constructor() { super(); } // 设置组件的内容 setContent(content: string) { this.innerHTML = content; } } // 注册自定义元素 customElements.define('router-view', RouterView); // 路由器实现 class WebComponentRouter { private routes: Record<string, Function>; private viewElement: RouterView | null; constructor() { this.routes = {}; this.viewElement = null; // 监听链接点击事件 document.addEventListener('router-navigate', ((e: CustomEvent) => { this.navigateTo(e.detail.to); }) as EventListener); // 初始化 window.addEventListener('load', this.handleLocationChange.bind(this)); window.addEventListener('popstate', this.handleLocationChange.bind(this)); } public setView(view: RouterView) { this.viewElement = view; this.handleLocationChange(); } private handleLocationChange() { const path = window.location.pathname; this.renderRoute(path); } public route(path: string, callback: Function) { this.routes[path] = callback; } private renderRoute(path: string) { if (this.viewElement && this.routes[path]) { const content = this.routes[path](); this.viewElement.setContent(content); } } public navigateTo(path: string) { history.pushState(null, '', path); this.renderRoute(path); } } // 使用示例 const router = new WebComponentRouter(); // 获取路由视图元素 const routerView = document.querySelector('router-view') as RouterView; router.setView(routerView); // 注册路由 router.route('/', () => '<h1>首页</h1>'); router.route('/about', () => '<h1>关于我们</h1>'); router.route('/contact', () => '<h1>联系我们</h1>'); // HTML中的使用 /* <body> <nav> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> <router-link to="/contact">联系</router-link> </nav> <router-view></router-view> </body> */ 实际框架中的路由实现 🔍 React Router简化版实现 // react-router-simple.tsx import React, { useState, useEffect, createContext, useContext, ReactNode } from 'react'; // 路由上下文 interface RouterContextType { currentPath: string; navigateTo: (path: string) => void; } const RouterContext = createContext<RouterContextType>({ currentPath: '/', navigateTo: () => {} }); // 路由器组件 interface RouterProps { children: ReactNode; } export const Router: React.FC<RouterProps> = ({ children }) => { const [currentPath, setCurrentPath] = useState(window.location.pathname); useEffect(() => { // 处理浏览器前进后退 const handlePopState = () => { setCurrentPath(window.location.pathname); }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, []); const navigateTo = (path: string) => { window.history.pushState(null, '', path); setCurrentPath(path); }; return ( <RouterContext.Provider value={{ currentPath, navigateTo }}> {children} </RouterContext.Provider> ); }; // 路由组件 interface RouteProps { path: string; component: React.ComponentType<any>; } export const Route: React.FC<RouteProps> = ({ path, component: Component }) => { const { currentPath } = useContext(RouterContext); // 简单的路径匹配 return currentPath === path ? <Component /> : null; }; // 链接组件 interface LinkProps { to: string; children: ReactNode; className?: string; } export const Link: React.FC<LinkProps> = ({ to, children, className }) => { const { navigateTo } = useContext(RouterContext); const handleClick = (e: React.MouseEvent) => { e.preventDefault(); navigateTo(to); }; return ( <a href={to} onClick={handleClick} className={className}> {children} </a> ); }; // 使用示例 const App = () => { return ( <Router> <nav> <Link to="/">首页</Link> <Link to="/about">关于</Link> <Link to="/contact">联系</Link> </nav> <div className="content"> <Route path="/" component={() => <h1>首页内容</h1>} /> <Route path="/about" component={() => <h1>关于我们</h1>} /> <Route path="/contact" component={() => <h1>联系我们</h1>} /> </div> </Router> ); }; Vue Router简化版实现 // vue-router-simple.ts import { ref, h, defineComponent, Component, VNode } from 'vue'; // 路由配置接口 interface RouteConfig { path: string; component: Component; } // 创建路由器 export function createRouter(options: { routes: RouteConfig[] }) { // 当前路径 const currentPath = ref(window.location.pathname); // 路由映射 const routeMap = new Map<string, Component>(); // 初始化路由表 for (const route of options.routes) { routeMap.set(route.path, route ponent); } // 处理路由变化 const handleRouteChange = () => { currentPath.value = window.location.pathname; }; // 监听popstate事件 window.addEventListener('popstate', handleRouteChange); // 路由导航方法 const push = (path: string) => { window.history.pushState(null, '', path); currentPath.value = path; }; // 路由替换方法 const replace = (path: string) => { window.history.replaceState(null, '', path); currentPath.value = path; }; // 创建并返回路由器实例 return { currentPath, routes: options.routes, routeMap, push, replace, install(app: any) { // 注册全局组件 app ponent('RouterLink', RouterLink); app ponent('RouterView', RouterView); // 提供路由器实例 app.provide('router', this); } }; } // RouterLink组件 const RouterLink = defineComponent({ name: 'RouterLink', props: { to: { type: String, required: true } }, setup(props, { slots }) { const router = inject('router') as ReturnType<typeof createRouter>; const handleClick = (e: Event) => { e.preventDefault(); router.push(props.to); }; return () => h( 'a', { href: props.to, onClick: handleClick }, slots.default && slots.default() ); } }); // RouterView组件 const RouterView = defineComponent({ name: 'RouterView', setup() { const router = inject('router') as ReturnType<typeof createRouter>; return () => { const currentComponent = router.routeMap.get(router.currentPath.value); return currentComponent ? h(currentComponent) : h('div', 'Not Found'); }; } }); // 使用示例 const Home = { template: '<div>Home Page</div>' }; const About = { template: '<div>About Page</div>' }; const router = createRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About } ] }); const app = createApp({ template: ` <div> <nav> <router-link to="/">Home</router-link> <router-link to="/about">About</router-link> </nav> <router-view></router-view> </div> ` }); app.use(router); app.mount('#app'); 最佳实践建议 ⭐ 路由设计原则保持URL语义化
使用清晰、描述性的URL遵循RESTful风格避免过长或复杂的路径使用连字符分隔单词(不用下划线)处理未匹配路由
提供默认的404页面重定向到主页或上一页清晰的错误提示提供导航建议合理使用路由参数
区分必要和可选参数使用查询参数处理筛选和排序参数命名清晰处理参数缺失情况路由权限管理
实现路由守卫基于角色的路由访问控制登录状态检查权限不足时提供反馈 实践示例 良好的路由组织结构 // 结构化路由配置 const routes = [ { path: '/', component: Layout, children: [ { path: '', component: Home, meta: { title: '首页', requiresAuth: false } }, { path: 'dashboard', component: Dashboard, meta: { title: '仪表盘', requiresAuth: true } } ] }, { path: '/user', component: UserLayout, children: [ { path: 'profile', component: UserProfile, meta: { title: '个人资料', requiresAuth: true } }, { path: 'settings', component: UserSettings, meta: { title: '账户设置', requiresAuth: true } } ] }, { path: '/auth', component: AuthLayout, children: [ { path: 'login', component: Login, meta: { title: '登录', guest: true } }, { path: 'register', component: Register, meta: { title: '注册', guest: true } } ] }, // 404页面应放在最后 { path: '*', component: NotFound, meta: { title: '页面未找到' } } ]; 权限控制 // 路由守卫实现 router.beforeEach((to, from, next) => { // 设置页面标题 document.title = to.meta.title ? `${to.meta.title} - 应用名称` : '应用名称'; // 权限控制 const isLoggedIn = !!localStorage.getItem('token'); // 需要登录但用户未登录 if (to.meta.requiresAuth && !isLoggedIn) { next({ path: '/auth/login', query: { redirect: to.fullPath } // 登录后重定向 }); return; } // 已登录用户不应访问游客页面(如登录页) if (to.meta.guest && isLoggedIn) { next('/dashboard'); return; } // 正常导航 next(); }); 路由过渡动画 // Vue中的路由过渡 const App = { template: ` <div class="app"> <transition name="fade" mode="out-in"> <router-view /> </transition> </div> ` }; // CSS /* .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } */ 动态路由加载 // 根据用户角色动态加载路由 function generateRoutesFromUserRole(role) { const asyncRoutes = [ { path: '/admin', component: Admin, meta: { roles: ['admin'] }, children: [ { path: 'users', component: UserManagement, meta: { roles: ['admin'] } }, { path: 'settings', component: SystemSettings, meta: { roles: ['admin'] } } ] }, { path: '/editor', component: Editor, meta: { roles: ['editor', 'admin'] } }, { path: '/user', component: User, meta: { roles: ['user', 'editor', 'admin'] } } ]; // 过滤适合当前用户角色的路由 const accessibleRoutes = filterRoutes(asyncRoutes, role); // 添加到路由器 router.addRoutes(accessibleRoutes); return accessibleRoutes; } function filterRoutes(routes, role) { return routes.filter(route => { if (route.meta && route.meta.roles) { // 检查当前用户角色是否匹配 return route.meta.roles.includes(role); } // 默认允许访问 return true; }).map(route => { // 递归处理子路由 if (route.children) { route.children = filterRoutes(route.children, role); } return route; }); } 结语 📝前端路由是现代单页应用的基础设施,掌握其实现原理和最佳实践可以帮助我们构建更高效、用户体验更佳的前端应用。通过本文,我们学习了:
前端路由的核心概念和工作原理两种主流的路由实现方式及其区别路由参数匹配、嵌套路由和路由守卫的实现如何构建路由组件主流框架中路由的实现细节💡 学习建议:
尝试手动实现一个简单的路由系统深入研究主流路由库的源码学习路由相关的性能优化技巧设计合理的路由结构来提升应用体验了解路由与状态管理的结合方式如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
工程化与框架系列(16)--前端路由实现由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“工程化与框架系列(16)--前端路由实现”
上一篇
蓝桥杯4T平台(串口打印电压值)
下一篇
安装Linux操作系统