🪜 写在前面
状态管理是前端架构中最容易“滥用”,却最影响维护性与性能的模块。
在实际开发中,你是否遇到这些问题:
- 页面状态和全局状态混乱在一起
- store 巨大无比,一个值改动引发全局重渲
- 表单页退出再进来,数据全部丢失
- 组件之间 props 和 emit 层层传递,毫无乐趣
本篇我们将:
- 拆解状态管理的分层思维
- 统一 Vue/Pinia/Vuex/Redux 的状态分布结构
- 给出状态归属、持久化、模块复用的实践方案
- 给出不同项目适用的架构模型
📦 一、状态管理的分类与归属建议
我们将状态拆成 4 层:
| 层级 | 状态类型 | 举例 | 建议位置 |
|---|---|---|---|
| ① 页面状态 | 表单输入、分页、loading | formData、currentPage | 组件内部 / setup() |
| ② 业务状态 | 当前用户信息、购物车内容 | userInfo、cartList | 模块 store(Pinia) |
| ③ 全局状态 | 多模块共享、应用级别 | 主题、token、权限 | 全局 store |
| ④ 派发逻辑 | 状态之间的数据流 | A 模块更新后通知 B | EventBus / 中间 store / 联动处理 |
✅ 二、推荐架构结构(以 Vue3 + Pinia 为例)
src/
├── modules/
│ └── user/
│ ├── views/...
│ ├── store.ts # 用户业务状态
│ ├── api.ts
├── store/ # 全局状态
│ ├── app.ts # 主题、语言
│ ├── auth.ts # 登录 token、权限列表
│ └── index.ts
示例:业务模块 store(Pinia)
// modules/user/store.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
info: null,
}),
actions: {
async fetch() {
this.info = await getUserInfo()
},
},
})
🧠 三、不同状态场景的最佳放置方案
🎯 场景1:表单输入数据(页面私有)
const formData = reactive({ name: '', age: '' })
// ✅ 不应放入 store
🎯 场景2:用户登录态(全局共享)
const authStore = useAuthStore()
const token = authStore.token
🎯 场景3:商品选择列表(多页面共享)
// 使用模块级 store:useGoodsStore()
🎯 场景4:页面跳转后状态还原
✅ 方法1:store 缓存 + 页面 onMounted 时恢复
✅ 方法2:使用 useRouteQuery(),在 query 中保持关键状态
✅ 方法3:sessionStorage 临时保存页面缓存数据
🔁 四、状态更新联动与事件分发策略
当模块 A 修改状态后,希望模块 B 响应更新,可以用:
✅ 方法1:订阅监听(Pinia)
// 在模块 B 中监听
userStore.$subscribe((mutation, state) => {
if (mutation.storeId === 'user' && mutation.events.key === 'info') {
console.log('用户信息变化了')
}
})
✅ 方法2:中间状态派发器(推荐)
创建一个通用的 eventStore:
export const useEventStore = defineStore('event', {
state: () => ({ events: {} }),
actions: {
fire(event, payload) {
this.events[event] = payload
},
clear(event) {
delete this.events[event]
},
},
})
模块 A:
eventStore.fire('refresh-table', { force: true })
模块 B:
watch(() => eventStore.events['refresh-table'], (val) => {
if (val?.force) refreshTable()
})
🧲 五、状态持久化方案(持久登录 / 记住用户)
使用 pinia-plugin-persistedstate 插件:
npm i pinia-plugin-persistedstate
在入口注册:
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPersist)
定义持久化字段:
export const useAuthStore = defineStore('auth', {
state: () => ({ token: '' }),
persist: true,
})
📊 六、Redux/Vuex/Pinia 对比与融合建议
| 特性 | Vuex | Pinia ✅ | Redux |
|---|---|---|---|
| 写法简洁 | ❌ | ✅ | ✅(Toolkit) |
| 类型支持 | 一般 | 强 | 强 |
| 分模块能力 | ✅ | ✅ | ✅ |
| DevTools 支持 | ✅ | ✅ | ✅ |
| 学习成本 | 中 | 低 | 高 |
| 推荐场景 | Vue2 老项目 | Vue3 全系 | React 中大型系统 |
🧩 七、统一状态管理实践建议
- 所有页面状态优先使用
setup内部ref/ reactive管理 - 所有共享业务状态都用模块化
store - 所有跨模块状态联动使用 eventStore / subscribe
- 不建议把“短生命周期状态”放入 store,例如临时分页配置、tabIndex
- store 命名统一采用功能命(
useUserStore、useAuthStore)
✅ 八、封装建议(高级实践)
useRouteQueryState():自动将分页等状态绑定到 URLusePersistedRef():封装 localStorage/SessionStorage 自动存取createScopedStore():为多 tab 设计私有状态pinia-auto-module:支持自动生成模块 + 动态加载 store
🧠 总结一句话
前端状态不是“能用就放 store”,而是“局部用局部管,模块用模块管,全局才上全局 store”。
接下来进入「表单系统架构与平台化设计」相关内容:
👉 《表单系统设计:动态表单 + 校验 + 可配置化》