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>

三、避坑指南(必看)

  1. 不要直接修改 State:直接写 store.state.menu.isCollapse = true 是错误的!必须通过 Mutations 修改,否则调试时找不到数据变更源头。

  2. Mutations 不写异步代码:setTimeout、接口请求等异步逻辑一定要放 Actions 里,否则 Vuex 无法追踪异步操作的数据变更。

  3. 模块必须加命名空间:拆分 Modules 时,namespaced: true 不能忘,否则多个模块的 toggleCollapse 等方法会冲突。

  4. 调用时带模块名:模块化后,调用任何方法都要加模块名前缀,如 store.commit('menu/toggleCollapse'),漏了模块名会报错。

  5. State/Getters 要加 computed:组件中使用 State 或 Getters 时,必须用 computed 包裹,否则数据变了组件不会更新。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐