Skip to content

Vuex 状态管理

本文介绍Vuex的核心概念、使用方法和最佳实践。Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

什么是Vuex?

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

为什么需要Vuex?

当应用变得复杂时,组件间共享状态的方式会变得混乱和难以维护。Vuex提供了一种集中式的状态管理方案,解决以下问题:

  • 多个组件共享同一状态时,难以跟踪状态的变化路径
  • 组件间通信变得复杂,特别是在多层嵌套组件中
  • 难以调试,无法追踪状态变化的历史

Vuex的核心概念

  • State:存储应用状态的单一对象
  • Getter:从state中派生出的计算属性
  • Mutation:修改state的唯一方法,必须是同步函数
  • Action:可以包含异步操作,通过提交mutation来修改state
  • Module:将store分割成模块,便于管理大型应用

安装与配置

安装Vuex

bash
# 使用npm
npm install vuex --save

# 使用yarn
yarn add vuex

基本配置

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    // 状态数据
  },
  mutations: {
    // 修改状态的方法
  },
  actions: {
    // 异步操作
  },
  getters: {
    // 计算属性
  },
  modules: {
    // 模块
  }
})

export default store

在Vue实例中使用

javascript
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

State

State是存储应用状态的单一对象,是应用中所有组件的数据源。

定义State

javascript
const store = new Vuex.Store({
  state: {
    count: 0,
    user: {
      name: 'John',
      age: 30
    },
    todos: [
      { id: 1, text: 'Learn Vuex', done: true },
      { id: 2, text: 'Build an app', done: false }
    ]
  }
})

访问State

在组件中访问

javascript
// 选项式API
this.$store.state.count

// 组合式API
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    console.log(store.state.count)
  }
}

使用mapState辅助函数

javascript
import { mapState } from 'vuex'

export default {
  computed: {
    // 对象展开运算符
    ...mapState([
      'count', // 映射 this.count 为 store.state.count
      'user'
    ]),
    // 重命名
    ...mapState({
      myCount: 'count', // 映射 this.myCount 为 store.state.count
      myUser: state => state.user
    })
  }
}

Getter

Getter用于从state中派生出一些状态,类似于组件中的计算属性。

定义Getter

javascript
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: 'Learn Vuex', done: true },
      { id: 2, text: 'Build an app', done: false }
    ]
  },
  getters: {
    // 基础用法
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    // 带参数的getter
    getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
    },
    // 使用其他getter
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    }
  }
})

访问Getter

javascript
// 直接访问
this.$store.getters.doneTodos

// 使用mapGetters辅助函数
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'doneTodos',
      'doneTodosCount'
    ]),
    // 重命名
    ...mapGetters({
      myDoneTodos: 'doneTodos'
    })
  }
}

Mutation

Mutation是修改state的唯一方法,必须是同步函数。

定义Mutation

javascript
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    // 基础用法
    increment(state) {
      // 变更状态
      state.count++
    },
    // 带参数的mutation
    incrementBy(state, payload) {
      state.count += payload.amount
    },
    // 通常使用对象风格的提交方式
    setUser(state, user) {
      state.user = user
    }
  }
})

提交Mutation

javascript
// 提交不带参数的mutation
this.$store.commit('increment')

// 提交带参数的mutation(载荷方式)
this.$store.commit('incrementBy', { amount: 10 })

// 对象风格的提交方式
this.$store.commit({
  type: 'incrementBy',
  amount: 10
})

// 使用mapMutations辅助函数
import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations([
      'increment', // 映射 this.increment() 为 this.$store.commit('increment')
      'setUser'
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }
}

Mutation的注意事项

  • Mutation必须是同步函数
  • 最好提前在你的store中初始化好所有所需属性
  • 使用常量替代Mutation事件类型可以使代码更清晰
javascript
// mutation-types.js
export const INCREMENT = 'increment'

export const SET_USER = 'setUser'

// store/index.js
import { INCREMENT, SET_USER } from './mutation-types'

const store = new Vuex.Store({
  mutations: {
    [INCREMENT](state) {
      // ...
    },
    [SET_USER](state, user) {
      // ...
    }
  }
})

Action

Action类似于Mutation,但可以包含异步操作,通过提交Mutation来修改state。

定义Action

javascript
const store = new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    increment(state) {
      state.count++
    },
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
    // 基础用法
    increment(context) {
      context.commit('increment')
    },
    // 解构参数
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    },
    // 带参数的action
    incrementBy({ commit }, payload) {
      commit('incrementBy', payload)
    },
    // 异步数据获取
    fetchUser({ commit }, userId) {
      return new Promise((resolve, reject) => {
        api.getUser(userId)
          .then(user => {
            commit('setUser', user)
            resolve(user)
          })
          .catch(error => {
            reject(error)
          })
      })
    }
  }
})

分发Action

javascript
// 分发action
this.$store.dispatch('increment')

// 分发带参数的action
this.$store.dispatch('incrementBy', { amount: 10 })

// 分发异步action
this.$store.dispatch('fetchUser', 123)
  .then(user => {
    console.log(user)
  })

// 使用mapActions辅助函数
import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions([
      'increment', // 映射 this.increment() 为 this.$store.dispatch('increment')
      'fetchUser'
    ]),
    ...mapActions({
      add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
    })
  }
}

Module

Module用于将store分割成模块,每个模块拥有自己的state、mutation、action、getter,甚至可以嵌套子模块。

定义Module

javascript
// store/modules/cart.js
const cart = {
  namespaced: true, // 开启命名空间
  state: () => ({ items: [] }),
  mutations: {
    addItem(state, item) {
      state.items.push(item)
    }
  },
  actions: {
    addItemAsync({ commit }, item) {
      setTimeout(() => {
        commit('addItem', item)
      }, 1000)
    }
  },
  getters: {
    itemCount(state) {
      return state.items.length
    }
  }
}

// store/modules/user.js
const user = {
  namespaced: true,
  state: () => ({ name: '' }),
  mutations: {
    setName(state, name) {
      state.name = name
    }
  }
}

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

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    cart,
    user
  }
})

使用Module

javascript
// 访问模块状态
this.$store.state.cart.items

// 提交模块mutation
this.$store.commit('cart/addItem', { id: 1, name: 'Product' })

// 分发模块action
this.$store.dispatch('cart/addItemAsync', { id: 1, name: 'Product' })

// 访问模块getter
this.$store.getters['cart/itemCount']

// 使用map辅助函数时指定模块
import { mapState, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState('cart', [
      'items'
    ])
  },
  methods: {
    ...mapActions('cart', [
      'addItemAsync'
    ])
  }
}

// 或者使用createNamespacedHelpers
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('cart')

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

高级特性

插件

Vuex允许你通过插件来扩展其功能。插件通常是一个函数,它接收store作为唯一参数。

javascript
// 日志插件
const logger = store => {
  // 当store初始化后调用
  store.subscribe((mutation, state) => {
    console.log('mutation', mutation)
    console.log('state', state)
  })
}

const store = new Vuex.Store({
  // ...
  plugins: [logger]
})

严格模式

在严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

javascript
const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

表单处理

在Vuex中处理表单输入时,可以使用v-model结合computed的getter和setter:

html
<input v-model="username">
javascript
export default {
  computed: {
    username: {
      get() {
        return this.$store.state.user.name
      },
      set(value) {
        this.$store.commit('setUserName', value)
      }
    }
  }
}

最佳实践

状态设计

  • 单一数据源:应用的所有状态都应集中在一个store中
  • 状态是只读的:唯一修改状态的方式是提交mutation
  • 使用常量命名mutation:便于协作和代码维护
  • 模块化:大型应用使用modules分割状态

项目结构

store/
├── index.js          # 入口文件,创建store
├── mutation-types.js # mutation类型常量
├── actions.js        # 根级action
├── mutations.js      # 根级mutation
├── getters.js        # 根级getter
└── modules/          # 模块目录
    ├── cart.js
    └── user.js

性能优化

  • 使用mapState、mapGetters等辅助函数减少重复代码
  • 对于大型应用,考虑使用模块的命名空间
  • 避免在getter中执行复杂计算,必要时进行缓存
  • 在开发环境使用严格模式,生产环境禁用

调试

  • 使用Vue Devtools:可以时间旅行调试,追踪状态变化
  • 开发环境启用严格模式
  • 使用日志插件记录mutation和状态变化

与Pinia的对比

Vuex 4是Vue 3兼容版本,而Pinia是Vue官方推荐的新一代状态管理库,具有以下优势:

  • 更简洁的API:不需要嵌套模块,不需要Mutation
  • 更好的TypeScript支持
  • 更轻量的体积
  • 更简单的组合式API集成

对于新项目,推荐使用Pinia;对于现有Vuex项目,可以继续使用或逐步迁移到Pinia。

总结

Vuex提供了一种集中式状态管理方案,通过State、Getter、Mutation、Action和Module等核心概念,帮助我们更好地管理应用状态。合理使用Vuex可以使应用状态变化更加可预测、可调试,同时提高代码的可维护性和复用性。在实际开发中,应根据项目规模和需求选择合适的状态管理方案,并遵循最佳实践进行开发。