出处:掘金
原作者:金泽宸
前端状态不是“能用就放 store”,而是“局部用局部管,模块用模块管,全局才上全局 store”
写在前面
状态管理是前端架构中最容易“滥用”,却最影响维护性与性能的模块
在实际开发中,你是否遇到这些问题:
- 页面状态和全局状态混乱在一起
- 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
// 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,
})
统一状态管理实践建议
- 所有页面状态优先使用
setup
内部ref/reactive
管理 - 所有共享业务状态都用模块化
store
- 所有跨模块状态联动使用 eventStore / subscribe
- 不建议把“短生命周期状态”放入 store,例如临时分页配置、tabIndex
- store 命名统一采用功能名(
useUserStore
、useAuthStore
)
封装建议(高级实践)
useRouteQueryState()
:自动将分页等状态绑定到 URLusePersistedRef()
:封装 localStorage/SessionStorage 自动存取createScopedStore()
:为多 tab 设计私有状态pinia-auto-module
:支持自动生成模块 + 动态加载 store