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'
    })
  }
}
相关推荐
我有满天星辰1 小时前
Vue 3 响应式双雄:ref 与 reactive 完全指南
前端·javascript·vue.js
leoZ23112 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
荒诞英雄16 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro16 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
Cobyte18 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
斯特凡今天也很帅19 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七20 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript
自然 醒1 天前
前端如何实现在线预览office常见文件功能?
前端·vue.js
肉肉不吃 肉1 天前
前端调试跨域如何解决
前端·vue.js
卓怡学长1 天前
w268基于springboot + vue 物流系统
java·数据库·vue.js·spring boot·spring·intellij-idea