跳到主要内容

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 触发 actionaction 里再去 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 就拿不到实例。

四、第三步:组件读取状态,常用 stategetters

组件接入 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 对应 action
  • action 可以写异步
  • action 自己通常不直接作为最终状态落点
  • 真正改 state 的动作,最终还是应该落到 mutation

七、第六步:状态变化后,组件自动响应更新

很多人会把 Vuex 的使用流程答到 commitdispatch 就结束了,其实最后一环别漏掉。

完整闭环应该是:

  1. 组件读取了 stategetters
  2. 组件和这些响应式数据建立依赖
  3. mutation 改掉对应状态后
  4. 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;组件里通过 stategetters 读取数据;同步修改走 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。
  • 什么时候该拆 module?
    • 当状态按业务域已经明显分层,例如用户、权限、购物车、订单,这时拆模块会更清晰。
  • Vuex 适不适合新项目?
    • 如果是 Vue 3 新项目,通常更常见的是选 Pinia;但面试题里谈 Vuex 时,要先把它自己的设计和流程讲清楚。

十一、易错点 / 坑

  • action 说成“专门修改 state 的地方”。
  • mutationaction 的职责混成一层。
  • 只会背四个模块名,讲不出它们之间的调用顺序。
  • 忽略“入口注册 store”这一步,直接从组件用法开始答。
  • 只答到 commit,没把“状态变化后视图自动更新”这条闭环补全。
  • 在模块化场景下忘记 namespaced 带来的调用路径变化。

速记要点

  • Vuex 是集中式状态管理,不只是全局变量。
  • 使用流程主线:创建 store -> 注册到应用 -> 组件读取 -> commit / dispatch -> 更新 state -> 视图刷新。
  • 读:state / getters
  • 改同步:commit -> mutation
  • 改异步:dispatch -> action -> commit -> mutation
  • 大项目通常会加 modulenamespace