Vue2中的Vuex

Published 2025-12-20 09:22 2479 words 13 min read

This post is not yet available in English. Showing the original.
一、为什么需要 Vuex?? 在 Vue2 项目开发中,如果你遇到以下问题,就该考虑使用 Vuex 了: 多个组件共享同一状态(如用户信息、购物车数据),组件间传参层层嵌套,代码冗余且易出错; 不同组件需要修改同一状态,通过事件总线(Event Bus)会导致数据流向混乱,难以调试; 希望状态变更可追踪,便于定位问题。 Vuex...

##一、为什么需要 Vuex??

在 Vue2 项目开发中,如果你遇到以下问题,就该考虑使用 Vuex 了:

  • 多个组件共享同一状态(如用户信息、购物车数据),组件间传参层层嵌套,代码冗余且易出错;

  • 不同组件需要修改同一状态,通过事件总线(Event Bus)会导致数据流向混乱,难以调试;

  • 希望状态变更可追踪,便于定位问题。

Vuex 把组件的共享状态抽取出来,以一个全局单例模式管理,让组件树构成一个巨大的“视图”,不管在哪个组件,都能获取状态或触发行为,同时保证所有状态的变更都遵循统一的规则,使代码可预测。

二、Vuex 的核心概念(Vue2 版本)

Vuex 的核心由 State、Getter、Mutation、Action、Module 五部分组成,我们先理清每个部分的作用:

1. State:唯一数据源

State 是 Vuex 存储状态(数据)的地方,相当于组件中的 data,但它是全局的,所有组件都能访问。

核心特点

  • 单一状态树:一个应用只有一个 Store 实例,所有状态集中管理;

  • 响应式:State 中的数据是响应式的,组件获取后,数据变更会自动更新视图。

2. Getter:计算属性

Getter 相当于 Vue 组件中的 computed,用于对 State 中的数据进行二次处理(如过滤、计算),结果会被缓存,只有依赖的数据变化时才会重新计算。

3. Mutation:唯一修改状态的方式

Mutation 是修改 State 的唯一入口,必须是同步函数(异步操作会导致状态变更无法追踪)。每个 Mutation 有一个字符串类型的 type 和一个 handler 函数,handler 函数接收 state 作为第一个参数,可接收第二个参数(载荷 payload)。

4. Action:处理异步操作

Action 用于处理异步逻辑(如接口请求),不能直接修改 State,必须通过提交 Mutation 来修改状态。Action 可以包含任意异步操作,支持提交多个 Mutation。

5. Module:模块化管理

当项目规模较大时,单一 State 会变得臃肿,Module 允许将 Store 分割成多个模块,每个模块拥有自己的 State、Getter、Mutation、Action,便于维护。

三、Vue2 中 Vuex 的使用步骤

1. 环境准备

首先确保项目是 Vue2 环境,安装对应版本的 Vuex(Vue2 对应 Vuex 3.x,Vue3 对应 Vuex 4.x):

# 安装 Vuex 3.x
npm install vuex@3 --save
# 或
yarn add vuex@3

2. 创建 Store 实例

在项目 src 目录下新建 store 文件夹,创建 index.js

// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

// 注册 Vuex
Vue.use(Vuex)

// 定义 Store
const store = new Vuex.Store({
  // 1. 状态数据
  state: {
    // 用户信息
    userInfo: {
      name: '',
      token: ''
    },
    // 购物车列表
    cartList: []
  },

  // 2. 计算属性
  getters: {
    // 计算购物车商品总数
    cartTotalCount: state => {
      return state.cartList.reduce((total, item) => total + item.quantity, 0)
    },
    // 过滤已选中的购物车商品
    selectedCartItems: state => {
      return state.cartList.filter(item => item.checked)
    }
  },

  // 3. 同步修改状态
  mutations: {
    // 设置用户信息
    SET_USER_INFO(state, payload) {
      state.userInfo = { ...state.userInfo, ...payload }
    },
    // 添加商品到购物车
    ADD_TO_CART(state, goods) {
      // 先判断商品是否已存在
      const existItem = state.cartList.find(item => item.id === goods.id)
      if (existItem) {
        existItem.quantity += 1
      } else {
        state.cartList.push({ ...goods, quantity: 1, checked: true })
      }
    },
    // 清空购物车
    CLEAR_CART(state) {
      state.cartList = []
    }
  },

  // 4. 异步操作
  actions: {
    // 异步获取用户信息(模拟接口请求)
    async fetchUserInfo({ commit }, userId) {
      try {
        // 模拟接口请求
        const res = await new Promise(resolve => {
          setTimeout(() => {
            resolve({
              name: '张三',
              token: 'abc123456'
            })
          }, 1000)
        })
        // 提交 Mutation 修改状态
        commit('SET_USER_INFO', res)
        return res
      } catch (error) {
        console.error('获取用户信息失败:', error)
        throw error
      }
    },
    // 异步清空购物车(模拟接口+修改状态)
    async clearCart({ commit }) {
      try {
        // 模拟调用清空购物车接口
        await new Promise(resolve => setTimeout(resolve, 500))
        // 提交 Mutation
        commit('CLEAR_CART')
      } catch (error) {
        console.error('清空购物车失败:', error)
      }
    }
  }
})

export default store

3. 在 Vue 项目中挂载 Store

修改 src/main.js,将 Store 挂载到 Vue 实例:

// src/main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 引入 Store

new Vue({
  el: '#app',
  store, // 挂载到Vue实例
  render: h => h(App)
})

4. 组件中使用 Vuex

(1)获取 State 数据

有两种方式:直接通过 this.$store.state 访问,或使用 mapState 辅助函数(推荐,简化代码)。

<template>
  <div class="user-info">
    <p>用户名:{{ userInfo.name }}</p>
    <p>购物车商品数:{{ cartList.length }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  computed: {
    // 方式1:直接访问
    // userInfo() {
    //   return this.$store.state.userInfo
    // },
    // cartList() {
    //   return this.$store.state.cartList
    // }

    // 方式2:mapState 辅助函数(推荐)
    ...mapState(['userInfo', 'cartList'])
  }
}
</script>

(2)使用 Getter

同理,可通过 this.$store.gettersmapGetters 辅助函数:

<template>
  <div class="cart">
    <p>购物车总数:{{ cartTotalCount }}</p>
    <p>已选中商品:{{ selectedCartItems.length }} 件</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['cartTotalCount', 'selectedCartItems'])
  }
}
</script>

(3)提交 Mutation 修改状态

Mutation 必须通过 commit 触发,支持 this.$store.commitmapMutations 辅助函数:

<template>
  <button @click="addGoods">添加商品到购物车</button>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  methods: {
    addGoods() {
      // 方式1:直接 commit
      // this.$store.commit('ADD_TO_CART', { id: 1, name: 'Vue实战教程', price: 99 })

      // 方式2:mapMutations 辅助函数
      this.ADD_TO_CART({ id: 1, name: 'Vue实战教程', price: 99 })
    },
    ...mapMutations(['ADD_TO_CART'])
  }
}
</script>

(4)触发 Action 处理异步

Action 通过 dispatch 触发,支持 this.$store.dispatchmapActions 辅助函数:

<template>
  <div>
    <button @click="getUserInfo">获取用户信息</button>
    <button @click="clearCart">清空购物车</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  methods: {
    async getUserInfo() {
      // 方式1:直接 dispatch
      // await this.$store.dispatch('fetchUserInfo', 1001)

      // 方式2:mapActions 辅助函数
      await this.fetchUserInfo(1001)
      console.log('用户信息已获取:', this.userInfo)
    },
    async clearCart() {
      await this.clearCart()
      console.log('购物车已清空')
    },
    ...mapActions(['fetchUserInfo', 'clearCart'])
  },
  computed: {
    userInfo() {
      return this.$store.state.userInfo
    }
  }
}
</script>

5. 模块化(Module)实战

当项目复杂时,拆分 Store 为多个模块,例如拆分 usercart 模块:

步 骤1:创建模块文件

// src/store/modules/user.js
export default {
  namespaced: true, // 开启命名空间,避免模块间命名冲突
  state: {
    userInfo: { name: '', token: '' }
  },
  getters: {
    isLogin: state => !!state.userInfo.token
  },
  mutations: {
    SET_USER_INFO(state, payload) {
      state.userInfo = { ...state.userInfo, ...payload }
    }
  },
  actions: {
    async fetchUserInfo({ commit }, userId) {
      const res = await new Promise(resolve => {
        setTimeout(() => resolve({ name: '张三', token: 'abc123' }), 1000)
      })
      commit('SET_USER_INFO', res)
    }
  }
}

// src/store/modules/cart.js
export default {
  namespaced: true,
  state: {
    cartList: []
  },
  getters: {
    cartTotalCount: state => state.cartList.reduce((t, i) => t + i.quantity, 0)
  },
  mutations: {
    ADD_TO_CART(state, goods) {
      const exist = state.cartList.find(i => i.id === goods.id)
      exist ? exist.quantity++ : state.cartList.push({ ...goods, quantity: 1 })
    }
  },
  actions: {
    async clearCart({ commit }) {
      await new Promise(resolve => setTimeout(resolve, 500))
      commit('CLEAR_CART')
    }
  }
}

步 骤2:整合模块到 Store

// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user,
    cart
  }
})

步 骤3:组件中使用命名空间模块

<template>
  <div>
    <p>用户名:{{ userInfo.name }}</p>
    <p>购物车总数:{{ cartTotalCount }}</p>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // 方式1:带命名空间的 mapState
    ...mapState('user', ['userInfo']),
    // 方式2:直接访问
    // userInfo() {
    //   return this.$store.state.user.userInfo
    // },
    ...mapGetters('cart', ['cartTotalCount'])
  },
  methods: {
    ...mapActions('user', ['fetchUserInfo']),
    ...mapActions('cart', ['clearCart']),
    async init() {
      await this.fetchUserInfo(1001)
    }
  },
  created() {
    this.init()
  }
}
</script>

四、Vuex 使用

1. 严格模式

开启严格模式后,任何不是通过 Mutation 修改 State 的操作都会抛出错误,便于调试(生产环境建议关闭):

const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production', // 仅开发环境开启
  // ...其他配置
})

2. 避免直接修改 State

永远不要在组件中直接修改 this.$store.state.xxx,必须通过 Mutation 或 Action 提交 Mutation,否则状态变更无法追踪。

3. 合理拆分模块

  • 按业务域拆分模块(如 user、cart、order),每个模块独立维护;

  • 开启 namespaced: true,避免命名冲突;

  • 复杂模块可进一步拆分(如 cart 下拆分子模块 cartItem、cartPrice)。

4. 异步逻辑统一放在 Action

所有异步操作(接口请求、定时器、Promise)都放在 Action 中,Mutation 只做同步的状态修改,保证状态变更可追踪。

5. 数据持久化

Vuex 状态刷新后会丢失,可结合 vuex-persistedstate 实现本地存储持久化:

npm install vuex-persistedstate@3 --save # 适配 Vuex 3.x
// src/store/index.js
import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
  // ...其他配置
  plugins: [
    createPersistedState({
      key: 'vue2-vuex-demo', // 本地存储的key
      paths: ['user.userInfo', 'cart.cartList'] // 需要持久化的状态路径
    })
  ]
})

6. 避免过度使用 Vuex

并非所有数据都要放入 Vuex,只有跨组件共享、全局使用的状态(如用户信息、购物车、全局配置)才适合,组件内部的私有状态(如表单临时值)直接用组件 data 即可。

五、常见问题与解决方案

1. 模块中获取根状态/根 Getter

在模块的 Getter/Action 中,可通过第二个参数获取根状态:

// 模块中的 Getter
getters: {
  // rootState 访问根状态,rootGetters 访问根 Getter
  cartTotalPrice: (state, getters, rootState, rootGetters) => {
    return state.cartList.reduce((t, i) => t + i.price * i.quantity, 0)
  }
}

// 模块中的 Action
actions: {
  async updateCart({ commit, rootState }) {
    // 访问根状态的 user 信息
    const token = rootState.user.userInfo.token
    await api.updateCart(token, state.cartList)
  }
}

2. 辅助函数简化命名空间写法

可通过绑定命名空间的方式简化代码:

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('cart')

export default {
  computed: {
    ...mapState(['cartList'])
  },
  methods: {
    ...mapActions(['addToCart'])
  }
}

六、总结

  1. Vuex 是 Vue2 中解决跨组件共享状态的核心方案,核心是“单向数据流”:View → Dispatch Action → Commit Mutation → Modify State → Update View;

  2. Vuex 3.x 适配 Vue2,核心概念包括 State(状态)、Getter(计算)、Mutation(同步修改)、Action(异步)、Module(模块化);

  3. “异步逻辑放 Action,同步修改放 Mutation”,复杂项目拆分模块并开启命名空间,结合 vuex-persistedstate 实现状态持久化;

  4. 避免过度使用 Vuex,仅管理全局共享状态,组件私有状态仍用 data 维护。

If you enjoyed this, leave a comment~