Vue 2 中 Store (Vuex) 从入门到精通

Vue 2 中 Store (Vuex) 从入门到精通

一、Vuex 核心概念与工作流

Vuex 是 Vue 2 官方推荐的状态管理模式,采用集中式存储管理应用的所有组件状态。我们可以把它理解为一个"全局数据仓库",配合严格的规则保证状态以可预测的方式发生变化。

核心工作流程

dispatch commit 修改 渲染 派生 计算属性 组件 Actions Mutations State Getters

核心概念解析

模块 类型 描述 是否同步 是否可修改 state
State 数据源 存储应用的状态数据 -
Getter 计算属性 对 state 进行派生计算
Mutation 同步方法 唯一能修改 state 的方式 ✅(唯一方式)
Action 异步方法 提交 mutation,处理异步操作 ❌(内部可异步) ❌(通过提交 mutation)
Module 分割模块 将 store 拆分为多个模块 -

二、Vuex 基础使用

1. 安装与配置

bash 复制代码
# Vue 2 必须安装 Vuex 3.x 版本
npm install vuex@3 --save
# 或
yarn add vuex@3

2. 创建 Store

src/store/index.js 中创建 store 实例:

javascript 复制代码
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  // 状态数据
  state: {
    count: 0,
    user: null
  },
  
  // 同步修改状态
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    SET_USER(state, user) {
      state.user = user
    }
  },
  
  // 异步操作
  actions: {
    login({ commit }, credentials) {
      return api.login(credentials).then(user => {
        commit('SET_USER', user)
      })
    }
  },
  
  // 计算属性
  getters: {
    doubleCount: state => state.count * 2,
    isLoggedIn: state => !!state.user
  },
  
  // 模块化(可选)
  modules: {
    // cart: cartModule
  }
})

3. 注入 Vue 实例

main.js 中挂载 store:

javascript 复制代码
import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  store,  // 注入所有组件
  render: h => h(App)
}).$mount('#app')

4. 在组件中使用

访问状态 (State)

vue 复制代码
<template>
  <div>
    <p>Count: {{ $store.state.count }}</p>
    <p>User: {{ $store.state.user?.name }}</p>
  </div>
</template>

使用 Getter

vue 复制代码
<template>
  <p>Double Count: {{ $store.getters.doubleCount }}</p>
  <p>Logged In: {{ $store.getters.isLoggedIn ? 'Yes' : 'No' }}</p>
</template>

提交 Mutation (同步)

vue 复制代码
<script>
export default {
  methods: {
    increment() {
      this.$store.commit('INCREMENT')
    },
    setUser(user) {
      this.$store.commit('SET_USER', user)
    }
  }
}
</script>

分发 Action (异步)

vue 复制代码
<script>
export default {
  methods: {
    handleLogin(credentials) {
      this.$store.dispatch('login', credentials)
        .then(() => {
          // 登录成功后的操作
        })
    }
  }
}
</script>

三、进阶使用:辅助函数与模块化

1. 辅助函数简化代码

Vuex 提供了 mapStatemapGettersmapMutationsmapActions 辅助函数,简化组件中对 Vuex 的使用。

vue 复制代码
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    // 映射 state
    ...mapState(['count', 'user']),
    // 映射 getters
    ...mapGetters(['doubleCount', 'isLoggedIn'])
  },
  methods: {
    // 映射 mutations
    ...mapMutations(['INCREMENT', 'SET_USER']),
    // 映射 actions
    ...mapActions(['login']),
    
    // 使用映射的方法
    handleIncrement() {
      this.INCREMENT()
    },
    handleLoginSubmit(credentials) {
      this.login(credentials)
    }
  }
}
</script>

2. 模块化管理

随着应用规模增长,单一 store 会变得臃肿,模块化是必然选择。

创建模块

javascript 复制代码
// store/modules/user.js
export default {
  namespaced: true,  // 启用命名空间,避免模块间命名冲突
  state: () => ({
    profile: null,
    permissions: []
  }),
  mutations: {
    SET_PROFILE(state, profile) {
      state.profile = profile
    },
    SET_PERMISSIONS(state, permissions) {
      state.permissions = permissions
    }
  },
  actions: {
    async fetchProfile({ commit }) {
      const profile = await api.getUserProfile()
      commit('SET_PROFILE', profile)
    }
  },
  getters: {
    hasPermission: (state) => (permission) => {
      return state.permissions.includes(permission)
    }
  }
}

注册模块

javascript 复制代码
// 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
  }
})

使用带命名空间的模块

vue 复制代码
<script>
import { mapActions, mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters('user', ['hasPermission'])
  },
  methods: {
    ...mapActions('user', ['fetchProfile']),
    
    checkPermission() {
      return this.hasPermission('edit-content')
    },
    loadUserProfile() {
      this.fetchProfile()
    }
  },
  created() {
    this.loadUserProfile()
  }
}
</script>

四、最佳实践与高级技巧

1. 使用常量命名 Mutations/Actions

javascript 复制代码
// store/mutation-types.js
export const SET_USER = 'SET_USER'
export const INCREMENT = 'INCREMENT'

// 在 mutations 中使用
import * as types from './mutation-types'

mutations: {
  [types.SET_USER](state, user) {
    state.user = user
  },
  [types.INCREMENT](state) {
    state.count++
  }
}

2. 状态持久化

Vuex 状态在页面刷新后会丢失,可通过 vuex-persistedstate 插件解决:

bash 复制代码
npm install vuex-persistedstate --save
javascript 复制代码
// store/index.js
import createPersistedState from 'vuex-persistedstate'

export default new Vuex.Store({
  // ...其他配置
  plugins: [
    createPersistedState({
      // 只持久化特定模块
      paths: ['user', 'cart'],
      // 使用 sessionStorage 而不是 localStorage
      storage: window.sessionStorage
    })
  ]
})

3. 严格模式

在开发环境启用严格模式,防止直接修改 state:

javascript 复制代码
export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  // ...其他配置
})

4. 调试工具

Vue DevTools 提供了强大的 Vuex 调试功能,包括:

  • 查看状态树
  • 追踪状态变化历史
  • 时间旅行调试(回溯到之前的状态)

确保在开发环境启用:

javascript 复制代码
// main.js
Vue.config.devtools = true;

五、常见问题与解决方案

1. 状态更新但视图未更新

原因:Vue 无法检测对象属性的直接添加/删除或数组索引的直接修改。

解决方案

javascript 复制代码
// 正确修改对象
this.$store.commit('UPDATE_USER', { ...state.user, name: 'New Name' })

// 正确修改数组
mutations: {
  ADD_ITEM(state, item) {
    state.items.push(item) // 使用数组方法
    // 或
    Vue.set(state.items, index, newValue)
  }
}

2. 模块化后辅助函数使用问题

解决方案:使用带命名空间的辅助函数或 createNamespacedHelpers

javascript 复制代码
import { createNamespacedHelpers } from 'vuex'

const { mapActions, mapGetters } = createNamespacedHelpers('user')

export default {
  computed: {
    ...mapGetters(['hasPermission'])
  },
  methods: {
    ...mapActions(['fetchProfile'])
  }
}

3. 跨模块通信

解决方案:使用 root 参数访问根模块

javascript 复制代码
// 在模块的 action 中访问根模块
actions: {
  async someAction({ commit, dispatch, rootState }) {
    // 访问根模块状态
    console.log(rootState.appVersion)
    
    // 调用根模块 action
    await dispatch('fetchGlobalData', null, { root: true })
    
    // 提交根模块 mutation
    commit('SET_GLOBAL_STATE', data, { root: true })
  }
}

六、Vuex 与 Vue Router 结合使用

在路由守卫中使用 Vuex 管理用户认证状态:

javascript 复制代码
// router/index.js
import VueRouter from 'vue-router'
import store from '@/store'

const router = new VueRouter({
  // 路由配置
})

router.beforeEach((to, from, next) => {
  // 检查路由是否需要认证
  if (to.matched.some(record => record.meta.requireAuth)) {
    // 从 Vuex 获取登录状态
    if (!store.getters.isLoggedIn) {
      // 未登录,重定向到登录页
      next({ path: '/login', query: { redirect: to.fullPath } })
    } else {
      // 已登录,检查权限
      if (to.meta.permission && !store.getters['user/hasPermission'](to.meta.permission)) {
        next({ path: '/403' })
      } else {
        next()
      }
    }
  } else {
    next()
  }
})

export default router
相关推荐
四瓣纸鹤2 小时前
从vue2和vue3的区别聊起
vue.js·状态模式
Lethehong2 小时前
React构建实时股票分析系统:蓝耘MaaS平台与DeepSeek-V3.2的集成实践
前端·react.js·前端框架·蓝耘mcp·蓝耘元生代·蓝耘maas
LSL666_2 小时前
1 验证码
java·服务器·前端·redis·验证码
Web打印2 小时前
HttpPrinter是一款基于HTTP协议的跨平台Web打印解决方案,
javascript·php
少油少盐不要辣2 小时前
前端如何处理AI模型返回的流数据
前端·javascript·人工智能
IT_陈寒2 小时前
Java21新特性实战:5个杀手级改进让你的开发效率提升40%
前端·人工智能·后端
跟着珅聪学java2 小时前
以下是使用JavaScript动态拼接数组内容到HTML的多种方法及示例:
开发语言·前端·javascript
BD_Marathon2 小时前
NPM_配置的补充说明
前端·npm·node.js
巴拉巴拉~~2 小时前
KMP 算法通用图表组件:KmpChartWidget 多维度可视化 + PMT 表渲染 + 性能对比
前端·javascript·microsoft