Vuex状态管理实战技巧与项目应用经验分享
优化前:卡得不行
项目上线三个月,用户反馈越来越多,最常听到的就是“点不动”“转圈半天”“手机上直接卡死”。我自己拿安卓机试了下,首页加载列表从5秒到8秒不等,滑动过程中 Vuex 状态更新一卡一卡的,甚至有时候点个按钮要等两秒才有反应。这哪是现代前端应用,简直是2003年的网页体验。
一开始以为是接口慢,但查了下 network 面板,数据1秒内就回来了。问题显然不在后端。然后怀疑是组件渲染太多,拆了几个大组件也没见好转。最后把目光锁定了 Vuex —— 因为整个项目用了全局状态管理,所有列表、筛选、用户信息都塞在里面,store 文件都快3000行了。
找到瘼颈了!
我上了 Vue Devtools,切到 State 面板,发现每次点击一个筛选项,整个 store 树都会重新 computed 一遍,尤其是那个叫 getAllItems 的 getter,它依赖了一个巨大的 rawList,而这个 list 有上万条数据。更离谱的是,我在 mutations 里打了个 log,发现一个简单的 UI 切换(比如显示/隐藏弹窗)居然也触发了这个 getter —— 明明和它没关系。
这时候我意识到:不是 Vuex 慢,是我用得太糙了。getter 全是同步计算,没有缓存控制,组件还到处用 mapGetters 盲目绑定,导致一点点状态变动,全站响应式都在重算。
我还用了 Performance 面板录了一段操作:打开列表页 → 点击筛选 → 滑动页面。结果吓一跳:60% 的时间花在 JavaScript 上,其中超过一半是 getter 计算和组件 diff。这根本不是用户体验可接受的范围。
动手改:先砍掉无脑 computed
第一刀我砍向了那个万恶的 getAllItems getter。原来它是这样写的:
// 优化前
getters: {
getAllItems: (state) => {
return state.rawList
.filter(item => item.status === 'active')
.map(item => ({
...item,
displayName: item.name || '未知'
}))
.sort((a, b) => a.sortIndex - b.sortIndex)
}
}
问题在哪?每一条数据变动,哪怕只是某个 item 的 status 改了,整个数组都要重新 filter、map、sort。上万条数据,浏览器直接拉爆。
我把它改成按需计算 + 缓存键控制:
// 优化后
getters: {
getAllItems: (state) => {
const cacheKey = ${state.filterStatus}-${state.sortBy}
if (state._cache[cacheKey]) {
return state._cache[cacheKey]
}
let result = state.rawList
if (state.filterStatus) {
result = result.filter(item => item.status === state.filterStatus)
}
result = result.map(item => ({
...item,
displayName: item.name || '未知'
}))
result.sort((a, b) => a[state.sortBy] - b[state.sortBy])
// 缓存最多两个 key,避免内存爆炸
state._cache = { [cacheKey]: result }
return result
}
}
同时在对应的 mutation 中清缓存:
mutations: {
SET_FILTER_STATUS(state, status) {
state.filterStatus = status
// 清缓存,下一次调用 getter 会重建
state._cache = {}
},
SET_SORT_BY(state, field) {
state.sortBy = field
state._cache = {}
}
}
注意这里我没有用 LRU 或其他复杂结构,因为场景简单,清掉就行。而且 state._cache 不会被持久化,不影响业务逻辑。
组件别再乱监听了
另一个大问题是:很多组件其实只关心是否 loading,却通过 mapGetters 绑定了整个列表。比如这个 Header 组件:
// 优化前
computed: {
...mapGetters(['getAllItems', 'getUserInfo', 'isInitialized'])
},
watch: {
getAllItems() {
this.updateCount()
}
}
即使 getAllItems 变了,Header 也不需要刷新,但它被绑了,所以每次列表变化它都 rerender。解决办法很简单:只监听你需要的状态。
// 优化后
computed: {
isInitialized() {
return this.$store.state.isInitialized
},
// 不再引入 getAllItems
},
methods: {
updateCount() {
const count = this.$store.getters.getAllItems.length
this.localCount = count
}
},
created() {
// 手动触发一次,之后靠事件通知或防抖更新
this.updateCount()
}
或者更进一步,用 shouldComponentUpdate 思路(Vue 里是 watch + immediate 控制),防抖更新:
watch: {
isInitialized: {
handler() {
this.debounceUpdate()
},
immediate: true
}
},
created() {
this.debounceUpdate = this._debounce(() => {
this.updateCount()
}, 300)
}
这里的 _debounce 是自己封装的简易防抖,你也可以用 lodash。
异步操作别堵在 action 里
还有一个隐藏坑:action 里堆了太多同步逻辑。比如一个初始化 action:
// 优化前
actions: {
async initApp({ commit, dispatch }) {
await dispatch('fetchUser')
await dispatch('fetchConfig')
await dispatch('fetchItemList') // 三者其实无依赖
commit('setInitialized', true)
}
}
这三个请求明明可以并行,却串行执行,白白多花了2秒。改成 Promise.all:
// 优化后
actions: {
async initApp({ commit, dispatch }) {
await Promise.all([
dispatch('fetchUser'),
dispatch('fetchConfig'),
dispatch('fetchItemList')
])
commit('setInitialized', true)
}
}
接口总耗时从 4.2s 降到 1.8s,效果立竿见影。
持久化也得小心
我们用了 vuex-persistedstate 把部分数据存 localStorage。但问题在于,每次 state 更新都同步写入,导致 IO 阻塞。特别是频繁变更的字段,比如临时表单数据。
解决方案:过滤掉不需要持久化的字段,并加个 debounce:
// 优化后
import createPersistedState from 'vuex-persistedstate'
import { _debounce } from '@/utils'
const debouncedSave = _debounce((key, value) => {
localStorage.setItem(key, value)
}, 500)
export default new Vuex.Store({
// ...
plugins: [
createPersistedState({
storage: {
getItem: (key) => localStorage.getItem(key),
setItem: (key, value) => {
debouncedSave(key, value)
},
removeItem: (key) => localStorage.removeItem(key)
},
reducer: (state) => ({
user: state.user,
config: state.config,
// 去掉 rawList、_cache 等大字段
})
})
]
})
这样既保证关键数据落地,又避免频繁写入拖慢主线程。
优化后:流畅多了
改完之后,我拿同一台安卓机再测:首页加载从平均 6.8s 降到 920ms,滑动过程帧率稳定在 50fps 以上,按钮点击响应几乎无延迟。Devtools 里 getter 计算次数减少了 80%,mutation 触发不再引发全量重算。
最明显的感受是:用户不再投诉卡顿了。产品经理甚至说“感觉像是换了框架”——其实我只是把原本该注意的地方补上了。
性能数据对比
- 首屏加载时间:6.8s → 920ms(降了 86%)
- JS 占比 CPU 时间:60% → 22%
- Getter 重计算频率:每次 mutation 都触发 → 仅关键参数变更时触发
- 内存占用峰值:380MB → 210MB
- 用户主动上报卡顿数:日均17次 → 日均2次
这些数字背后是实实在在的用户体验提升。虽然还有一些小瑕疵,比如冷启动时 still 有点白屏感,但已经不影响核心流程了。
结语
以上就是我这次 Vuex 性能优化的全过程。没有上什么高大上的库,也没有重构架构,就是一点一点看瓶颈、改代码、测数据。中间踩过好几次坑,比如缓存没清导致数据不一致,debounce 没控好让状态延迟更新,都是线上灰度才发现的。
这个方案不是最优解,但最简单、最可控,适合中后台这种快速迭代的项目。如果你有更好的方式,比如用 Pinia 迁移或者局部状态提升,欢迎评论区交流。我也在考虑下一步要不要动这块。
开发就是这样,永远在修修补补中前进。这次优化完,终于能睡个安稳觉了。

暂无评论