很多 Vue 项目都会遇到一个奇怪的问题:
Vuex 管 全局状态 很好用。
但一旦开始管理 页面状态,事情就变得越来越混乱。
例如:
- 用户离开页面再回来,筛选条件还在
- store 里出现一堆页面 module
- mutation 越来越多,但很多只是 UI 状态
慢慢地 Vuex store 变成:
一个巨大的状态仓库
但这些状态其实只属于:
当前页面
问题其实不在 Vuex。
而在于:
Vue 应用里缺了一层状态模型。
Vue 应用其实有三类状态
如果从架构角度看,一个 Vue 应用的状态其实可以分为三类:
Component State
↓
Page State
↓
Global State
Component State
组件内部状态。
例如:
- dropdown 展开
- input 值
- hover 状态
特点:
- 生命周期 = 组件
- 不需要共享
通常写在:
data
setup
Global State
全局状态。
例如:
- 用户信息
- 权限
- 主题
- 应用配置
特点:
- 全局共享
- 生命周期 = 应用
这类状态非常适合 Vuex / Pinia。
Page State
页面级状态。
例如:
- 筛选条件
- tab
- 分页
- 图表数据
- 当前查询结果
特点:
- 多组件共享
- 生命周期 = 页面
一个典型例子:
筛选条件变化时:
- 图表刷新
- 表格刷新
- 指标刷新
多个组件同时响应。
Vuex 的问题:生命周期不匹配
Vuex 本质上是:
Application Store
生命周期:
应用启动 → 应用关闭
而页面状态生命周期是:
进入页面 → 离开页面
这两者不匹配。
这会导致几个常见问题。
问题一:状态残留
用户操作流程:
页面A → 页面B → 再回到页面A
如果页面状态在 Vuex 中:
筛选条件仍然存在。
有时这是 bug。
例如:
用户打开一个新的分析页面,却继承了旧筛选条件。
解决方法通常是:
beforeDestroy() {
resetStore()
}
但问题是:
每个页面都要写 reset。
写漏一次就是 bug。
问题二:store module 膨胀
当复杂页面越来越多时,store 会变成:
store/
funnel
dashboard
analysis
report
问题是:
这些 module 99% 的时间不需要存在。
但 Vuex store 是应用级的。
所以它们会一直存在。
久而久之 store 会变成:
一个巨大的状态仓库。
问题三:Vuex 仪式感过重
Vuex 修改状态的流程:
commit
↓
mutation
↓
state
这对于全局状态来说是合理的。
但对于页面内部交互来说:
太重。
例如:
筛选条件变化
其实只需要:
store.filters = newFilters
但 Vuex 要写:
commit('SET_FILTERS')
这种 ceremony 在复杂页面中会迅速膨胀。
问题四:页面通信不方便
在复杂页面中,组件之间经常需要通信。
例如:
- filter change
- tab change
- chart update
如果用 Vuex:
组件 A:
commit('setFilters')
组件 B:
watch(() => store.filters)
很多时候只是组件之间简单通信。
Vuex 在这里显得过于笨重。
Vuex 并没有错
需要说明一点:
Vuex 并没有设计错误。
Vuex 的设计目标是:
Global State Management
也就是:
应用级状态管理。
它解决的是:
Application State
而不是:
Page Runtime State
Vue 应用缺失的一层
如果从系统结构看,Vue 应用其实应该是三层:
Component State
↓
Page Runtime State
↓
Global State
Vuex / Pinia 解决了:
Global State
但中间这一层:
Page Runtime State
一直没有标准方案。
这也是为什么很多团队会:
- 把页面状态塞进 Vuex
- 使用 EventBus
- 或者 props drilling
每种方案都不完美。
一个更合理的模型
如果引入一个概念:
Page Runtime Context
结构会变成:
Component State
↓
Page Runtime Context
↓
Global Store
也就是:
- 组件状态 → 组件内部
- 页面状态 → 页面上下文
- 全局状态 → Vuex
每一层只管理属于自己的状态。
总结
Vuex 管不好页面状态,并不是因为 Vuex 设计不好。
而是因为:
Vuex 解决的是 Global State,而不是 Page State。
Vue 应用其实有三层状态:
Component State
↓
Page Runtime State
↓
Global State
Vuex / Pinia 只覆盖了第三层。
中间这一层:
Page Runtime Context
一直是空白。
而复杂系统的问题,往往就出现在这里。