Vue3+Vuex 状态管理知识点
Vuex:知识点+实例详解
一、前置认知:Vuex 到底是什么?
1.1 核心定义
Vuex 是 Vue 官方提供的全局状态管理工具,作用是集中存储和管理整个应用中多个组件共享的数据(比如用户信息、菜单状态、标签页数据等),并规范数据的修改流程,避免组件间传值混乱。
1.2 核心解决的问题
没有 Vuex 时,组件间传值的痛点:
-
父传子靠
props,子传父靠$emit,跨3层以上组件时需“层层传递”,代码冗余 -
非父子组件(如兄弟组件、跨层级组件)传值需用“事件总线”,后期难以维护
-
多个组件修改同一数据时,无法追踪“谁改了数据”,调试困难
Vuex 解决方案:把共享数据放进“全局仓库(Store)”,组件直接从仓库取数据、通过规范流程改数据,不用互相通信。
1.3 核心概念总览
Vuex 有5个核心概念,先记住“数据流向”:组件 → Actions(可选) → Mutations → State → 组件,后续逐个拆解。
| 概念 | 核心作用 |
|---|---|
| State | 存储共享数据的“容器” |
| Mutations | 修改 State 数据的“唯一入口”(同步操作) |
| Actions | 处理异步/复杂逻辑,间接修改 State(调用 Mutations) |
| Getters | 对 State 数据加工处理(类似组件的计算属性) |
| Modules | 拆分仓库为多个模块(解决大型项目仓库臃肿) |
二、核心知识点+实例
先明确基础目录结构(新手直接复制这个结构到项目中):
src/
└── store/ # 仓库总目录
├── index.js # 仓库入口(创建 Store 实例)
└── modules/ # 模块目录(大型项目用,先从单模块学)
└── menu.js # 示例模块(管理菜单、标签页数据)
2.1 State:共享数据的“存储容器”
知识点讲解
-
定义:State 是 Vuex 中专门存储共享数据的地方,类似组件中的
data,但它是全局的,所有组件都能访问。 -
核心规则:State 中的数据不能直接修改,必须通过 Mutations 修改(保证数据变更可追踪)。
-
定义格式:在模块文件(如 menu.js)中定义为普通对象,键是数据名,值是初始值;必须导出供其他文件使用。
-
调用格式:Vue3 中通过
useStore()获取仓库实例,再通过store.state.模块名.数据名访问;必须用computed包裹保证“响应式”(数据变了组件自动更新)。
实操例子
步骤1:定义 State(menu.js)
// store/modules/menu.js
// 定义共享数据(菜单状态、标签页数据)
const state = {
isCollapse: false, // 菜单折叠状态(初始:不折叠)
openedTabs: [], // 已打开的标签页(初始:空数组)
activeTab: '' // 当前激活的标签页(初始:空)
}
// 导出 State,供模块和仓库使用
export default state
步骤2:在组件中调用 State(Aside.vue 侧边栏组件)
<template>
<div class="aside">
<p>菜单状态:{{ isCollapse ? '折叠' : '展开' }}</p>
<div class="menu" v-if="!isCollapse">
<div class="menu-item">首页</div>
<div class="menu-item">用户管理</div>
</div>
</div>
</template>
<script setup>
// 1. 引入 Vuex 工具和响应式工具
import { computed } from 'vue'
import { useStore } from 'vuex'
// 2. 获取仓库实例
const store = useStore()
// 3. 调用 State 中的数据(用 computed 包裹保证响应式)
// 格式:computed(() => store.state.模块名.数据名)
const isCollapse = computed(() => store.state.menu.isCollapse)
</script>
2.2 Mutations:修改 State 的“唯一入口”
知识点讲解
-
定义:Mutations 是修改 State 数据的唯一合法途径,类似给 State 配了“专属钥匙”,只有它能修改数据。
-
核心规则:Mutations 中只能写同步代码(如赋值、数组操作),不能写异步代码(如 setTimeout、接口请求)。
-
定义格式:在模块文件中定义为对象,键是方法名,值是函数;函数有两个固定参数:
state:当前模块的 State 对象(Vuex 自动传入,直接用它修改数据) -
payload:可选参数,组件调用时传入的“要修改的值”(可以是简单值或对象)
调用格式:通过 store.commit('模块名/方法名', 可选参数) 调用;也可通过 mapMutations 辅助函数简化。
实操例子
步骤1:定义 Mutations(menu.js)
// store/modules/menu.js
import state from './state' // 导入 State(也可直接写在同一文件)
// 定义修改 State 的方法
const mutations = {
// 1. 无参数方法:切换菜单折叠状态
toggleCollapse(state) { // state 是固定第一个参数
state.isCollapse = !state.isCollapse // 直接修改 State
},
// 2. 有参数方法:添加标签页(payload 是标签页信息对象)
addTab(state, payload) { // payload 是组件传入的参数
// 避免重复添加同一标签页
const isExist = state.openedTabs.some(tab => tab.path === payload.path)
if (!isExist) {
state.openedTabs.push(payload) // 往数组中添加标签页
}
state.activeTab = payload.path // 更新当前激活标签
}
}
export default mutations
步骤2:在组件中调用 Mutations(Aside.vue 侧边栏)
<template>
<div class="aside">
<button @click="handleToggleCollapse">
{{ isCollapse ? '展开' : '折叠' }}菜单
</button>
<div class="menu" v-if="!isCollapse">
<div class="menu-item" @click="handleAddTab">首页</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const isCollapse = computed(() => store.state.menu.isCollapse)
// 1. 调用无参数的 Mutations 方法
const handleToggleCollapse = () => {
// 格式:store.commit('模块名/方法名')
store.commit('menu/toggleCollapse')
}
// 2. 调用有参数的 Mutations 方法(传标签页信息)
const handleAddTab = () => {
const tabInfo = {
path: '/home',
name: 'Home',
meta: { title: '首页' } // 标签页标题
}
// 格式:store.commit('模块名/方法名', 传入的参数)
store.commit('menu/addTab', tabInfo)
}
</script>
2.3 Actions:处理异步的“中间桥梁”
知识点讲解
-
定义:Actions 用于处理异步操作(如接口请求、定时器)或复杂业务逻辑,它不能直接修改 State,必须通过调用 Mutations 来修改。
-
核心作用:承接组件的请求,处理完异步/复杂逻辑后,“委托” Mutations 去修改 State(相当于“中间客服”)。
-
定义格式:在模块文件中定义为对象,键是方法名,值是函数;函数有两个固定参数:
context:上下文对象(Vuex 自动传入),包含commit(调用 Mutations 的方法)、state(当前 State)等;常用解构{ commit }简化。 -
payload:可选参数,组件传入的请求参数(如接口 ID、标签页路径)。
调用格式:通过 store.dispatch('模块名/方法名', 可选参数) 调用;支持异步(可加 await 等待结果)。
实操例子
步骤1:定义 Actions(menu.js)
// store/modules/menu.js
// 定义处理异步/复杂逻辑的方法
const actions = {
// 1. 同步逻辑(简单转发,实际用得少,主要看格式)
toggleCollapse({ commit }) { // 解构 context 拿到 commit 方法
commit('toggleCollapse') // 调用 Mutations 方法修改 State
},
// 2. 异步逻辑(核心场景:调接口获取标签页数据后添加)
async fetchTabAndAdd({ commit }, payload) { // payload 是接口参数
try {
// 模拟接口请求(实际用 axios,这里用 fetch 演示异步)
const res = await fetch(`/api/tab/${payload.id}`) // 等待接口返回
const tabData = await res.json() // 解析接口数据(标签页信息)
// 接口成功后,调用 Mutations 把数据存入 State
commit('addTab', tabData)
// 可选:返回结果给组件(告诉组件“操作成功”)
return tabData
} catch (err) {
// 接口失败处理
console.error('获取标签页失败', err)
throw err // 把错误抛给组件,让组件显示提示
}
},
// 3. 复杂逻辑(删除标签页时判断并返回新激活标签)
removeTab({ commit, state }, payload) { // 解构拿到 commit 和 state
const currentActive = state.activeTab // 先获取当前激活标签
// 逻辑判断:如果删除的是当前标签,要切换到上一个
const newActiveTab = state.openedTabs.find(tab => tab.path !== payload)?.path || '/'
// 调用 Mutations 删除标签页
commit('removeTab', payload)
// 返回新激活标签给组件
return newActiveTab
}
}
// 记得在模块中导出(后面讲 Modules 时统一整合)
export default actions
步骤2:在组件中调用 Actions(NavHeader.vue 头部标签栏)
<template>
<div class="nav-header">
<button @click="handleAddTabByApi">添加首页标签(接口)</button>
<button @click="handleRemoveTab('/home')">删除首页标签</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
const store = useStore()
const router = useRouter()
// 1. 调用异步 Actions 方法(调接口添加标签页)
const handleAddTabByApi = async () => {
try {
// 格式:await store.dispatch('模块名/方法名', 接口参数)
const tabData = await store.dispatch('menu/fetchTabAndAdd', { id: 1 })
console.log('标签页添加成功', tabData)
} catch (err) {
alert('添加失败:' + err.message)
}
}
// 2. 调用带返回值的 Actions 方法(删除标签页并跳转)
const handleRemoveTab = async (path) => {
// 调用 Actions 删标签,拿到新激活标签路径
const newActiveTab = await store.dispatch('menu/removeTab', path)
// 跳转到新激活的标签页
router.push(newActiveTab)
}
</script>
2.4 Getters:State 数据的“加工工厂”
知识点讲解
-
定义:Getters 用于对 State 中的原始数据进行加工处理(如过滤、计算、格式化),类似组件中的
computed计算属性。 -
核心优势:加工逻辑只写一次,多个组件可复用;会缓存加工结果,只有依赖的 State 数据变化时才重新计算,性能更好。
-
定义格式:在模块文件中定义为对象,键是方法名,值是函数;函数有两个固定参数:
state:当前模块的 State 对象(获取原始数据) -
getters:可选参数,可调用其他 Getters 加工后的结果(实现链式加工)。
调用格式:通过 store.getters['模块名/方法名'] 调用;带参数的 Getters 需返回函数接收参数。
实操例子
步骤1:定义 Getters(menu.js)
// store/modules/menu.js
// 定义数据加工方法
const getters = {
// 1. 基础加工:直接返回 State 数据(统一管理,方便后续修改规则)
isCollapse: state => state.isCollapse,
// 2. 计算加工:判断是否有打开的标签页(返回布尔值)
hasOpenedTabs: state => {
return state.openedTabs.length > 0 // 原始数组 → 布尔值
},
// 3. 过滤加工:提取所有标签页的标题(返回新数组)
tabTitles: state => {
// 原始标签页对象数组 → 只包含标题的数组
return state.openedTabs.map(tab => tab.meta.title)
},
// 4. 带参数加工:根据标签名找具体标签(返回函数接收参数)
getTabByName: state => (name) => {
return state.openedTabs.find(tab => tab.name === name)
},
// 5. 依赖其他 Getters:计算标签页数量(用已加工的结果)
tabCount: (state, getters) => {
// 如果有标签页(hasOpenedTabs 为 true),返回数量,否则返回 0
return getters.hasOpenedTabs ? state.openedTabs.length : 0
}
}
export default getters
步骤2:在组件中调用 Getters(NavHeader.vue)
<template>
<div class="nav-header">
<p>是否有打开的标签:{{ hasOpenedTabs ? '是' : '否' }}</p>
<p>标签页数量:{{ tabCount }}</p>
<p>所有标签标题:{{ tabTitles.join(', ') }}</p>
<p>首页标签信息:{{ homeTab }}</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// 1. 调用普通 Getters(用 computed 包裹保证响应式)
const hasOpenedTabs = computed(() => store.getters['menu/hasOpenedTabs'])
const tabCount = computed(() => store.getters['menu/tabCount'])
const tabTitles = computed(() => store.getters['menu/tabTitles'])
// 2. 调用带参数的 Getters(传入参数“Home”找首页标签)
const homeTab = computed(() => {
return store.getters['menu/getTabByName']('Home')
})
</script>
2.5 Modules:大型项目的“仓库拆分工具”
知识点讲解
-
定义:Modules 用于将大型项目的 Store 拆分为多个“子仓库(模块)”,每个模块管理一类相关数据(如 menu 模块管菜单,user 模块管用户信息)。
-
核心配置:每个模块必须添加
namespaced: true开启“命名空间”,避免不同模块的 Mutations/Actions 方法名冲突。 -
模块结构:每个模块包含独立的
state、mutations、actions、getters,可单独写在不同文件,也可整合在一个文件。 -
注册格式:在仓库入口(index.js)中导入所有模块,通过
modules选项注册到总 Store 中。
实操例子
步骤1:整合模块(menu.js 完整结构)
// store/modules/menu.js
// 1. 模块的 State
const state = {
isCollapse: false,
openedTabs: [],
activeTab: ''
}
// 2. 模块的 Mutations
const mutations = {
toggleCollapse(state) {
state.isCollapse = !state.isCollapse
},
addTab(state, payload) {
const isExist = state.openedTabs.some(tab => tab.path === payload.path)
if (!isExist) state.openedTabs.push(payload)
state.activeTab = payload.path
},
removeTab(state, payload) {
const index = state.openedTabs.findIndex(tab => tab.path === payload)
if (index > -1) state.openedTabs.splice(index, 1)
if (payload === state.activeTab) {
state.activeTab = state.openedTabs[index - 1]?.path || '/'
}
}
}
// 3. 模块的 Actions
const actions = {
toggleCollapse({ commit }) {
commit('toggleCollapse')
},
async fetchTabAndAdd({ commit }, payload) {
// 省略接口请求逻辑(同前面例子)
}
}
// 4. 模块的 Getters
const getters = {
hasOpenedTabs: state => state.openedTabs.length > 0,
tabCount: state => state.openedTabs.length
}
// 5. 导出模块(必须开启命名空间)
export default {
namespaced: true, // 关键:避免模块间方法冲突
state,
mutations,
actions,
getters
}
步骤2:注册模块(index.js 仓库入口)
// store/index.js
// 1. 导入 Vuex 创建工具和模块
import { createStore } from 'vuex'
import menu from './modules/menu' // 导入菜单模块
import user from './modules/user' // 导入用户模块(可新增)
// 2. 创建总 Store 实例并注册模块
export default createStore({
modules: {
menu, // 注册菜单模块,使用时模块名为“menu”
user // 注册用户模块,使用时模块名为“user”
}
})
步骤3:在组件中调用多模块数据(User.vue)
<template>
<div class="user">
<p>用户名:{{ userName }}</p>
<p>菜单状态:{{ isCollapse ? '折叠' : '展开' }}</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// 调用不同模块的 Getters
const userName = computed(() => store.getters['user/userName'])
const isCollapse = computed(() => store.getters['menu/isCollapse'])
</script>
三、避坑指南(必看)
-
不要直接修改 State:直接写
store.state.menu.isCollapse = true是错误的!必须通过 Mutations 修改,否则调试时找不到数据变更源头。 -
Mutations 不写异步代码:setTimeout、接口请求等异步逻辑一定要放 Actions 里,否则 Vuex 无法追踪异步操作的数据变更。
-
模块必须加命名空间:拆分 Modules 时,
namespaced: true不能忘,否则多个模块的toggleCollapse等方法会冲突。 -
调用时带模块名:模块化后,调用任何方法都要加模块名前缀,如
store.commit('menu/toggleCollapse'),漏了模块名会报错。 -
State/Getters 要加 computed:组件中使用 State 或 Getters 时,必须用
computed包裹,否则数据变了组件不会更新。
更多推荐

所有评论(0)