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
相关推荐
一 乐19 分钟前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
C_心欲无痕1 小时前
ts - tsconfig.json配置讲解
linux·前端·ubuntu·typescript·json
清沫1 小时前
Claude Skills:Agent 能力扩展的新范式
前端·ai编程
yinuo2 小时前
前端跨页面通信终极指南:方案拆解、对比分析
前端
北辰alk2 小时前
Vue 模板引擎深度解析:基于 HTML 的声明式渲染
vue.js
北辰alk2 小时前
Vue 自定义指令完全指南:定义与应用场景详解
vue.js
yinuo2 小时前
前端跨页面通讯终极指南⑨:IndexedDB 用法全解析
前端
北辰alk2 小时前
Vue 动态路由完全指南:定义与参数获取详解
vue.js
北辰alk2 小时前
Vue Router 完全指南:作用与组件详解
vue.js
北辰alk2 小时前
Vue 中使用 this 的完整指南与注意事项
vue.js