Vue Router 与状态管理:参数、导航和 Pinia 选型
本文目录
原始笔记日期为 2018-08-05;本文于 2026-10-20 重新整理。旧示例主要来自 Vue Router 3 与 Vuex 时代;新代码示例按 Vue Router 4 和 Vue 3 编写,迁移前必须核对项目锁文件。
先确认路由、框架和服务器配置
Vue Router 3、Vue Router 4 与 Vue 2/3 项目之间存在兼容边界,不能仅凭文章发布日期推断正在运行的 API。先核对 vue、vue-router 的锁定版本、项目采用 Options API 还是 Composition API,以及构建工具和部署方式。
History 模式可提供干净的路径,但用户直接刷新嵌套路由时,Web 服务器必须把应用路径回退到 SPA 入口;静态资源路径和 API 路径应按实际架构分别配置,不能把所有 404 都重写成应用首页。
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/users/:id',
name: 'user-detail',
component: () => import('./views/UserDetail.vue'),
},
],
});
声明式链接与程序化导航
站内链接优先使用 <RouterLink>,它能保留路由语义和浏览器交互;需要在事件处理后导航时,使用路由实例的 push、replace、back 或 go。
<RouterLink
:to="{ name: 'user-detail', params: { id: '42' }, query: { tab: 'posts' } }"
>
查看用户
</RouterLink>
router.push({
name: 'user-detail',
params: { id: '42' },
query: { tab: 'posts' },
});
router.replace({ path: '/login', query: { redirect: '/books/' } });
router.back();
router.push加一条历史记录,返回按钮通常会回到上一个位置。router.replace替换当前历史位置,适合不希望返回到某个中间状态的场景。router.back()等价于后退一步;router.go(n)按历史记录前进或后退。- Options API 组件通过
this.$router导航、通过this.$route读取当前路由;Composition API 分别使用useRouter()和useRoute()。
params、query 与路由复用
| 数据 | 示例 | 适用场景 |
|---|---|---|
| 动态 params | /users/:id 对应 route.params.id | 路由资源标识,通常参与匹配路径 |
| query | /search?q=vue&page=2 对应 route.query | 可分享的筛选、搜索和分页状态 |
| hash | /docs#install 对应 route.hash | 页面内锚点或可分享的小节位置 |
需要用 params 生成动态路径时,优先通过具名路由导航。params 不会与 path 一起解析:例如传入 { path: '/users', params: { id: 42 } } 不会自动得到 /users/42。若直接拼路径,必须逐个编码动态路径片段;query 值由路由器序列化,但仍应限制长度和允许值。
不要把密码、验证码、访问令牌或其他敏感数据放进 query 或 hash。URL 会进入浏览器历史、服务器日志、分析系统和复制分享链接。
从 /users/1 导航到 /users/2 时,路由通常复用同一个组件实例。若详情数据依赖参数,要监听具体的 route.params.id 或使用更新守卫;请求要能取消或忽略过期响应,避免较慢的旧请求覆盖新用户数据。更完整的守卫返回值和鉴权边界见导航守卫指南。
路由守卫不是授权系统
全局 beforeEach、单路由 beforeEnter 和组件更新/离开守卫可以改善导航体验,例如跳到登录页或提醒未保存草稿。但浏览器代码可被绕过,守卫不构成资源保护:后端仍须对每个请求验证当前身份、角色、资源归属和操作权限。
登录后返回目标要经过应用内路径校验,避免开放重定向;公开页、登录页、无权限页和未知路径的行为也需要覆盖测试。仅隐藏菜单或按钮不能防止直接调用 API。
Vuex 与 Pinia:全局状态要有所有者
组件自身的状态优先留在组件内;需要在非相邻页面间共享且确有统一所有权时,再提取 store。不要为了避免传一个 prop 就把所有数据都放进全局状态。
| 能力 | Vuex 历史模型 | Pinia / 新项目方向 |
|---|---|---|
| 状态 | state | state,并为 SSR 请求创建独立 store |
| 派生值 | getters | getters |
| 修改流程 | 组件 dispatch action;action 可异步,再 commit 同步 mutation | 通过 store action 组织操作;不要求单独的 mutation 层 |
| 适用判断 | 稳定运行的旧项目可继续维护 | Vue 官方推荐新项目评估 Pinia;Vuex 进入维护模式 |
Vuex 的 mutation 应同步,异步流程由 action 组织;strict mode 用于发现绕过 mutation 的直接修改,不是生产环境权限或数据完整性保障。不要把 action 的“可异步”误解成可以脱离状态所有权随意改写数据。
Pinia 最小示例:
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count += 1;
},
},
});
持久化与 SSR 边界
Pinia/Vuex 中的状态默认属于当前运行中的应用实例;页面刷新后不会自动变成持久数据。若确实要保存用户偏好,明确数据分类、过期策略、版本迁移和异常解析;不要把前端持久化当成服务端真相,也不要存放可长期复用的敏感凭据。
SSR 服务器会复用已加载模块。把按用户变化的数据放在模块级单例中可能造成跨请求状态污染;每个请求都应创建独立应用、路由器和 store。服务端授权仍须重新读取可信账号状态,不能把浏览器 store 当作身份凭证。
验收清单
- 从站内链接、程序化导航、浏览器前进/后退和直接刷新分别验证目标路由。
- 参数变化时确认数据重新加载,慢响应不会覆盖新路由内容。
- query 中只保留可分享、非敏感且长度受限的状态。
- 守卫覆盖公开页、登录返回、取消导航和未知路径;服务端另做独立权限测试。
- store 只管理真正共享的状态;Vuex 旧模块与新 Pinia 代码边界清楚。
- SSR 下用两个合成用户请求验证 store、路由和页面数据互不串用。
官方资料
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。