Vuex 的使用流程
面试里如果被问“Vuex 怎么用”,不要只背 state / getters / mutations / actions 四件套。更稳的答法是把它讲成一条完整链路:
- 先创建 store,集中定义共享状态
- 再在应用入口注册,让组件树都能拿到 store
- 组件读取时走
state/getters - 修改同步状态走
commit -> mutation - 处理异步逻辑走
dispatch -> action -> commit -> mutation - 状态变了以后,依赖它的组件会重新渲染
如果你能把这条链路顺下来,面试官通常会觉得你是真的用过,而不是只背过 API。
本文默认语境:Vuex 3 / 4。两者核心使用流程基本一致,主要区别在于接入方式:
- Vue 2 常见是
Vue.use(Vuex)+new Vue({ store })- Vue 3 常见是
createApp(App).use(store)
面试速答(30 秒版 TL;DR)
- Vuex 的使用流程本质上就是:定义共享状态 -> 挂到应用 -> 组件读取 -> 通过统一入口修改 -> 驱动视图更新。
- 读数据主要走两类入口:
- 原始状态走
state - 派生状态走
getters
- 原始状态走
- 改数据要分两种:
- 同步修改走
commit触发mutation - 异步流程走
dispatch触发action,action里再去commit mutation
- 同步修改走
- 最核心的约束是:不要在组件里随意改共享状态,而是把状态变更收敛到 Vuex 规定的链路里。
- 面试一句话总结可以直接背:
组件读 state/getters,改同步状态用 commit,改异步流程用 dispatch,最终都落到 mutation 改 state。
一、先建立心智模型:Vuex 是“集中式状态仓库”
很多人第一次学 Vuex,会把它理解成“一个全局对象”。这个理解不算错,但太粗。
更准确的说法是:
- Vuex 是一个集中式状态管理模式
- 它把多个组件共享的数据抽到统一仓库里管理
- 它不只负责“存数据”,还负责规定“怎么读、怎么改、怎么追踪”
所以它的重点不是“全局可访问”,而是“全局可管理”。
最经典的四个部分分别负责:
state:源数据getters:基于状态计算出来的派生数据mutations:同步修改状态的唯一正式入口actions:处理异步、业务编排,再去提交 mutation
二、第一步:创建 Store,先把共享状态收进去
Vuex 的第一步永远不是写组件,而是先定义 store。
一个最小示例:
import { createStore } from 'vuex'
export default createStore({
state() {
return {
count: 0,
user: null as null | { id: number; name: string },
}
},
getters: {
isLogin(state) {
return !!state.user
},
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
},
},
actions: {
async fetchUser({ commit }) {
const user = await Promise.resolve({ id: 1, name: 'Terry' })
commit('setUser', user)
},
},
})
这一层做的事,本质上是把“共享状态”和“状态变更规则”集中声明出来。
面试时可以顺手补一句:
state解决“数据放哪”getters解决“数据怎么算”mutations解决“数据怎么改”actions解决“异步和业务流程怎么串”
三、第二步:在应用入口注册 Store
只有创建 store 还不够,你还得把它注入到整个应用里,组件才能访问。
1. Vue 2 常见写法
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
import store from './store'
Vue.use(Vuex)
new Vue({
store,
render: (h) => h(App),
}).$mount('#app')
2. Vue 3 常见写法
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
这一步的本质是:
- 把 store 挂到应用上下文
- 让后代组件都能通过注入机制访问它
如果入口没注册,组件里调用 useStore() 或访问 $store 就拿不到实例。
四、第三步:组件读取状态,常用 state 和 getters
组件接入 Vuex 后,第一类操作是“读”。
1. 直接读 state
<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const count = computed(() => store.state.count)
</script>
适合:
- 原始状态
- 不需要额外加工的数据
2. 读 getters
<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const isLogin = computed(() => store.getters.isLogin)
</script>
适合:
- 多个组件都要复用的派生逻辑
- 不希望把计算规则散落在各个组件里的场景
这里面试常见追问是:为什么不把所有计算都写组件里?
标准答法是:
- 组件内计算适合局部视图逻辑
getters更适合“多个组件共享的派生状态规则”- 它能让状态规则集中,减少重复实现
五、第四步:同步修改状态要走 commit -> mutation
Vuex 最重要的使用约束,就是不要在组件里直接改共享状态,而是通过 commit 提交 mutation。
示例:
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
function add() {
store.commit('increment')
}
</script>
对应的 mutation:
mutations: {
increment(state) {
state.count++
}
}
这条链路为什么重要?
- 修改入口统一
- DevTools 更容易追踪是谁改了状态
- 团队协作时约束更清晰
也就是说,mutation 不只是“多写一层代码”,它本质上是在建立一条可审计的状态变更路径。
六、第五步:异步流程走 dispatch -> action -> commit
mutation 要求是同步的,所以接口请求、延时任务、组合多个 mutation 的业务编排,通常放在 action 里。
示例:
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
async function loadUser() {
await store.dispatch('fetchUser')
}
</script>
对应 action:
actions: {
async fetchUser({ commit }) {
const user = await api.getUser()
commit('setUser', user)
}
}
这一步最容易答混,面试里最好直接说清楚:
dispatch对应actionaction可以写异步action自己通常不直接作为最终状态落点- 真正改
state的动作,最终还是应该落到mutation
七、第六步:状态变化后,组件自动响应更新
很多人会把 Vuex 的使用流程答到 commit 或 dispatch 就结束了,其实最后一环别漏掉。
完整闭环应该是:
- 组件读取了
state或getters - 组件和这些响应式数据建立依赖
- 当
mutation改掉对应状态后 - Vue 触发依赖更新,组件重新渲染
所以 Vuex 不是“改完后手动通知组件”,而是借助 Vue 的响应式系统自动更新视图。
这也是为什么 Vuex 经常被理解成:
- 上层负责“状态组织和变更约束”
- 底层仍然依赖 Vue 响应式机制完成视图刷新
八、模块化场景下,使用流程会多一层 namespace
项目变大后,通常不会把所有状态都塞进一个文件,而会拆成模块。
例如:
const store = createStore({
modules: {
user: userModule,
cart: cartModule,
},
})
如果模块开启命名空间:
const userModule = {
namespaced: true,
state: () => ({ profile: null }),
mutations: {
setProfile(state, profile) {
state.profile = profile
},
},
}
那么调用链会变成:
store.commit('user/setProfile', profile)
store.dispatch('user/fetchProfile')
这说明 Vuex 的使用流程本质没变,只是多了一个“模块作用域”。
面试里可以这样总结模块化:
- 小项目:一套根 store 就够
- 大项目:按领域拆 module
- 拆模块后,读取和提交通常要带 namespace
九、典型题 & 标准答法
Q1:Vuex 的完整使用流程是什么?
答:先创建 store,把共享状态、getters、mutations、actions 定义好;然后在应用入口注册 store;组件里通过 state 或 getters 读取数据;同步修改走 commit mutation;异步逻辑走 dispatch action,再由 action 提交 mutation 改 state;state 变化后,依赖它的组件自动重新渲染。
Q2:为什么异步不直接写在 mutation 里?
答:因为 Vuex 设计上要求 mutation 保持同步,这样 DevTools 才能清楚记录一次状态变更前后发生了什么。如果 mutation 里混入异步,状态变化时机就不可预测,调试和追踪都会变差,所以异步一般放在 action。
Q3:组件里能不能直接改 store.state?
答:技术上有些场景可能“看起来能改”,但不推荐。Vuex 的核心价值就是把状态修改统一收敛到 mutation,这样链路清晰、可追踪、便于协作。直接改等于绕开规范,会让状态来源变乱。
Q4:getters 和组件里的 computed 有什么区别?
答:两者都能做派生计算,但侧重点不同。组件里的 computed 更偏局部视图逻辑,getters 更偏共享状态上的公共计算规则。如果多个组件都依赖同一套派生逻辑,放在 getters 更合适。
十、常见追问
- Vuex 3 和 Vuex 4 的主要差异是什么?
- 核心使用模式几乎一致,主要是 Vue 2 / Vue 3 适配方式不同。
- Vuex 和 Pinia 的根本区别是什么?
- Vuex 强调
mutation约束和集中式模块树,Pinia 更轻量,不强制单独一层 mutation。
- Vuex 强调
- 什么时候该拆 module?
- 当状态按业务域已经明显分层,例如用户、权限、购物车、订单,这时拆模块会更清晰。
- Vuex 适不适合新项目?
- 如果是 Vue 3 新项目,通常更常见的是选 Pinia;但面试题里谈 Vuex 时,要先把它自己的设计和流程讲清楚。
十一、易错点 / 坑
- 把
action说成“专门修改 state 的地方”。 - 把
mutation和action的职责混成一层。 - 只会背四个模块名,讲不出它们之间的调用顺序。
- 忽略“入口注册 store”这一步,直接从组件用法开始答。
- 只答到
commit,没把“状态变化后视图自动更新”这条闭环补全。 - 在模块化场景下忘记
namespaced带来的调用路径变化。
速记要点
- Vuex 是集中式状态管理,不只是全局变量。
- 使用流程主线:创建 store -> 注册到应用 -> 组件读取 ->
commit/dispatch-> 更新 state -> 视图刷新。 - 读:
state/getters。 - 改同步:
commit -> mutation。 - 改异步:
dispatch -> action -> commit -> mutation。 - 大项目通常会加
module和namespace。