Vuex构建可维护的 Vue.js 状态管理

一、为什么需要 Vuex?

1.1 状态管理痛点

在大型 Vue 应用中,组件间共享状态变得复杂:

  • 多层嵌套组件传值繁琐

  • 兄弟组件通信困难

  • 多个组件依赖同一状态

  • 不同视图需要变更同一状态

1.2 Vuex 是什么?

Vuex 是 Vue.js 的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

二、核心概念详解

2.1 Store(仓库)

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {      // 状态数据
    count: 0,
    user: null
  },
  mutations: {  // 同步修改状态
    increment(state) {
      state.count++
    }
  },
  actions: {    // 异步操作
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {    // 计算属性
    doubleCount: state => state.count * 2
  },
  modules: {    // 模块分割
    user: userModule
  }
})
复制代码

2.2 State(状态)

定义: 存储应用状态的数据

javascript 复制代码
state: {
  todos: [
    { id: 1, text: '学习 Vuex', done: true },
    { id: 2, text: '项目实战', done: false }
  ],
  visibilityFilter: 'SHOW_ALL',
  count: 0,
  user: null
}

组件中访问:

javascript 复制代码
// 方式1:直接访问
computed: {
  count() {
    return this.$store.state.count
  }
}

// 方式2:mapState 辅助函数
import { mapState } from 'vuex'

export default {
  computed: {
    // 映射 this.count 为 store.state.count
    ...mapState(['count']),
    
    // 重命名
    ...mapState({
      storeCount: 'count',
      // 使用函数
      todosCount: state => state.todos.length
    })
  }
}

2.3 Getters(派生状态)

定义: 基于 state 的计算属性

javascript 复制代码
getters: {
  // 基本 getter
  doneTodos: state => {
    return state.todos.filter(todo => todo.done)
  },
  
  // 接收其他 getter 作为第二个参数
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  },
  
  // 返回函数实现传参
  getTodoById: state => id => {
    return state.todos.find(todo => todo.id === id)
  }
}

组件中使用:

javascript 复制代码
computed: {
  // 方式1:直接访问
  doneTodos() {
    return this.$store.getters.doneTodos
  },
  
  // 方式2:mapGetters 辅助函数
  ...mapGetters(['doneTodos', 'doneTodosCount']),
  
  // 方式3:重命名
  ...mapGetters({
    finishedTodos: 'doneTodos'
  }),
  
  // 带参数的 getter
  todo() {
    return this.$store.getters.getTodoById(2)
  }
}

2.4 Mutations(变更)

定义: 唯一修改 state 的方法(必须是同步)

javascript 复制代码
mutations: {
  // 基本 mutation
  increment(state) {
    state.count++
  },
  
  // 携带参数
  addTodo(state, todo) {
    state.todos.push(todo)
  },
  
  // 对象风格的提交
  updateUser(state, payload) {
    state.user = {
      ...state.user,
      ...payload
    }
  }
}

组件中提交:

javascript 复制代码
methods: {
  // 方式1:直接提交
  increment() {
    this.$store.commit('increment')
  },
  
  addTodo() {
    this.$store.commit('addTodo', {
      id: 3,
      text: '新的任务',
      done: false
    })
  },
  
  // 对象风格提交
  updateUser() {
    this.$store.commit('updateUser', {
      name: '张三',
      age: 25
    })
  },
  
  // 方式2:mapMutations 辅助函数
  ...mapMutations(['increment']),
  ...mapMutations({
    add: 'addTodo'  // 重命名
  })
}

2.5 Actions(动作)

定义: 处理异步操作和业务逻辑

javascript 复制代码
actions: {
  // 基本 action
  incrementAsync({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  },
  
  // 异步 API 调用
  async fetchUser({ commit }, userId) {
    try {
      const response = await api.getUser(userId)
      commit('SET_USER', response.data)
      return response.data
    } catch (error) {
      commit('SET_ERROR', error.message)
      throw error
    }
  },
  
  // 组合多个 action
  async login({ dispatch, commit }, credentials) {
    // 1. 显示加载状态
    commit('SET_LOADING', true)
    
    try {
      // 2. 调用 API
      const user = await dispatch('authenticate', credentials)
      
      // 3. 获取用户信息
      await dispatch('fetchUserInfo', user.id)
      
      // 4. 跳转页面(在组件中处理更好)
      return user
    } finally {
      // 5. 隐藏加载状态
      commit('SET_LOADING', false)
    }
  }
}

组件中分发:

javascript 复制代码
methods: {
  // 方式1:直接分发
  fetchData() {
    this.$store.dispatch('fetchUser', 123)
      .then(user => {
        console.log('获取成功', user)
      })
      .catch(error => {
        console.error('获取失败', error)
      })
  },
  
  // 方式2:mapActions 辅助函数
  ...mapActions(['incrementAsync', 'fetchUser']),
  ...mapActions({
    getUser: 'fetchUser'  // 重命名
  }),
  
  // 异步处理
  async handleLogin() {
    try {
      const user = await this.login(credentials)
      this.$router.push('/dashboard')
    } catch (error) {
      this.showError(error.message)
    }
  }
}

三、Module(模块化)

3.1 为什么需要模块?

当应用变得复杂时,单一的 store 文件会变得臃肿。模块允许我们将 store 分割成小模块。

3.2 基本模块结构

javascript 复制代码
// store/modules/user.js
const userModule = {
  // 开启命名空间(推荐)
  namespaced: true,
  
  state: () => ({
    profile: null,
    token: '',
    permissions: []
  }),
  
  mutations: {
    SET_PROFILE(state, profile) {
      state.profile = profile
    },
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  
  actions: {
    async login({ commit }, credentials) {
      const response = await api.login(credentials)
      commit('SET_PROFILE', response.user)
      commit('SET_TOKEN', response.token)
      return response
    },
    
    logout({ commit }) {
      commit('SET_PROFILE', null)
      commit('SET_TOKEN', '')
      localStorage.removeItem('token')
    }
  },
  
  getters: {
    isAuthenticated: state => !!state.token,
    userName: state => state.profile?.name || 'Guest'
  }
}

export default userModule

3.3 注册模块

javascript 复制代码
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import userModule from './modules/user'
import productModule from './modules/product'
import cartModule from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  // 根 state
  state: {
    appName: '电商系统'
  },
  
  // 注册模块
  modules: {
    user: userModule,
    product: productModule,
    cart: cartModule
  }
})

3.4 模块访问方式

javascript 复制代码
// 开启命名空间时的访问
computed: {
  // state 访问
  profile() {
    return this.$store.state.user.profile
  },
  
  // getter 访问
  isAuthenticated() {
    return this.$store.getters['user/isAuthenticated']
  }
},

methods: {
  // action 调用
  login() {
    this.$store.dispatch('user/login', credentials)
  },
  
  // mutation 提交
  updateProfile() {
    this.$store.commit('user/SET_PROFILE', newProfile)
  }
}

3.5 模块辅助函数

javascript 复制代码
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // 方式1:直接映射
    ...mapState('user', ['profile', 'token']),
    ...mapGetters('user', ['isAuthenticated', 'userName']),
    
    // 方式2:使用 createNamespacedHelpers
    ...mapState({
      userProfile: state => state.user.profile,
      cartItems: state => state.cart.items
    })
  },
  
  methods: {
    ...mapActions('user', ['login', 'logout']),
    ...mapActions('cart', ['addToCart']),
    
    // 重命名
    ...mapActions({
      userLogin: 'user/login',
      addProduct: 'cart/addToCart'
    })
  }
}
相关推荐
用户982450514184 小时前
vue3响应式解构注意
vue.js
不会敲代码14 小时前
🚀 从DOM操作到Vue3:一个Todo应用的思维革命
vue.js
未来龙皇小蓝5 小时前
RBAC前端架构-02:集成Vue Router、Vuex和Axios实现基本认证实现
前端·vue.js·架构
晓得迷路了5 小时前
栗子前端技术周刊第 116 期 - 2025 JS 状态调查结果、Babel 7.29.0、Vue Router 5...
前端·javascript·vue.js
淡忘_cx5 小时前
使用Jenkins自动化部署vue项目(2.528.2版本)
vue.js·自动化·jenkins
iDao技术魔方5 小时前
深入Vue 3响应式系统:为什么嵌套对象修改后界面不更新?
javascript·vue.js·ecmascript
念念不忘 必有回响6 小时前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
吹牛不交税16 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore
MZ_ZXD00117 小时前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
_codemonster19 小时前
Vue的三种使用方式对比
前端·javascript·vue.js