本文目录导读:
- 结构与URL一一对应(Restful 风格 & 语义化)
- 扁平化优先,但支持嵌套
- 唯一性 & 无歧义
- 状态在URL,不在内存(可分享、可刷新)
- 关注点分离:路由 vs. 组件
- 容错性:404 与 默认路由
- 架构一致性 & 扩展性
- 一个优秀的路由设计自查清单
这是一个非常专业且核心的前端工程问题,前端路由设计的好坏,直接决定了应用的可维护性、可扩展性以及用户体验。
以下是前端路由设计的核心原则和最佳实践:
结构与URL一一对应(Restful 风格 & 语义化)
这是最基础也是最重要的原则,URL应该清晰地反映页面的内容层级和资源关系。
- 好例子(语义化):
/users—— 用户列表/users/123—— 用户ID为123的详情页/articles/2024/tech-routing—— 某年某分类下的文章
- 坏例子(无意义):
/page1,/page2,/detail?id=123(查询参数滥用)/index.jsp?module=user&action=view(类似后端MVC的旧式风格)
原则: 将路由视为资源的地址,而不是页面的地址。
扁平化优先,但支持嵌套
- 扁平化(推荐): 尽可能将路由设计在2-3层以内,太深的嵌套(如
/a/b/c/d/e)会使URL冗长,且增加路由匹配的复杂度和状态管理难度。- 优点: 易于理解、便于持久化(如分享URL)、参数传递清晰。
- 例子:
/settings/profile比/user/1/settings/profile更好(除非必须有父级上下文)。
- 嵌套(谨慎使用): 仅在确实存在父子包含关系时使用,一个用户管理页面,左侧是用户列表,右侧是用户详情。
- 例子:
/users->/users/:id(这是合理的嵌套)
- 例子:
唯一性 & 无歧义
每个逻辑页面应该有且只有一条路由路径。
- 避免重复入口: 不要同时存在
/dashboard和/home指向同一个组件。 - 处理可选参数:如果有
/users和/users/:id,要明确/users是否应该展示一个默认用户(通常不应该,而是列表页)。 - 规范重定向:如果有历史遗留或别名路由(如旧版
/profile-> 新版/user),必须通过301重定向(对SPA来说是路由守卫跳转)处理,避免SEO和用户体验问题。
状态在URL,不在内存(可分享、可刷新)
这是前端路由与后端路由最大的不同。用户应该能通过URL直接恢复页面状态(包括弹窗、Tab切换、分页、搜索条件等)。
- 坏例子:
- 用户点击“第3页”,URL不变(仍然是
/list),页面数据存在React State或Vuex/Pinia里,用户刷新页面后回到第1页。
- 用户点击“第3页”,URL不变(仍然是
- 好例子:
- 用户点击“第3页”,URL变为
/list?page=3&keyword=前端,刷新页面后,URL不变,页面恢复到第3页。
- 用户点击“第3页”,URL变为
原则: 任何导致页面视图或数据变化的用户操作,如果值得被用户记住或分享,就应该体现在URL中(作为路径参数或查询参数)。
关注点分离:路由 vs. 组件
路由负责“导航到哪里”,组件负责“显示什么”。
- 不要将业务逻辑写在路由配置里: 路由配置里只需要定义路径、组件、守卫(权限检查),不需要写数据请求、状态管理代码。
- 路由守卫(Guard)专注权限: 路由守卫只做一件事:判断是否可以进入/离开,一旦决定可以进入,就把控制权交给组件,不要在路由守卫里发起异步数据请求(这会导致白屏loading时间变长,应该放在组件内部的
beforeRouteEnter或onBeforeMount中)。 - 懒加载: 每个路由对应的组件应该是异步加载的(
() => import(‘./UserDetail.vue’)),这是现代前端框架的标准实践,直接影响首屏加载性能。
容错性:404 与 默认路由
- 全局404路由: 必须有一个
path: '*'或path: '/:pathMatch(.*)'的路由,捕获所有未匹配路径,展示一个友好的“页面不存在”页面,而不是白屏或控制台报错。 - 根路由重定向: 不应该直接渲染一个空白页,要么是首页(
/home),要么重定向到某个默认页面(如/redirect->/dashboard)。 - 权限不足: 设计一个统一的“无权限”或“403”页面,而不是让用户看到空白页或无限重定向。
架构一致性 & 扩展性
- 声明式配置(路由表): 所有路由应该集中在一个或几个配置文件中,而不是散落在组件或代码逻辑里。
- 模块化: 对于大型应用,按照业务模块拆分路由配置(如
userRoutes.js,articleRoutes.js),最后在主路由中合并。// 推荐结构 const routes = [ ...userRoutes, // 展开用户模块的路由 ...articleRoutes, // 展开文章模块的路由 { path: '/:pathMatch(.*)', component: NotFound } // 全局404放在最后 ]; - 命名路由: 给每个路由定义一个唯一的
name,在代码中使用router.push({ name: ‘UserDetail’, params: { id } })而非router.push(’/user/${id}’),这可以让你在修改URL结构(如从/user/:id改为/u/:id)时,无需修改所有页面跳转代码。
一个优秀的路由设计自查清单
- URL是否简洁、语义化、可读? (用户能不能理解这个URL大概是什么内容?)
- 删除一个路由或修改路径名,是否困难? (是否大量依赖字符串拼接而不是命名路由?)
- 如果用户复制当前URL发给朋友,朋友打开后能看到一样的页面吗? (包括Tab、分页、筛选状态?)
- 应用是否有一个友好的404页面?
- 路由守卫是否只做权限判断而没有发异步请求?
- 是否所有页面组件都做到了懒加载?
遵循这些原则,你的前端应用会变得更健壮、更容易被搜索引擎收录,并且更易于团队协作和后期维护。
标签: 状态同步