# Vuex状态管理指南

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

# 什么是状态管理

在Vue应用中,组件之间共享状态(数据)的几种常见方式:

  1. Props向下传递 - 父组件向子组件传递数据
  2. 事件向上传递 - 子组件通过事件向父组件发送消息
  3. 事件总线/发布订阅 - 任意组件间通信
  4. 依赖注入(provide/inject) - 深层组件通信
  5. 中央存储(Vuex) - 集中管理共享状态

随着应用复杂度增加,组件层级变深,简单的共享方式变得难以维护。Vuex为解决这一问题而生。

# Vuex核心概念

# 1. State - 单一状态树

Vuex使用单一状态树,即一个对象包含应用所有的状态:

const store = new Vuex.Store({
  state: {
    count: 0,
    todos: [],
    user: null,
    products: []
  }
})
1
2
3
4
5
6
7
8

在组件中访问状态:

<template>
  <div>计数: {{ count }}</div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

使用mapState辅助函数简化访问:

import { mapState } from 'vuex'

export default {
  computed: {
    // 映射this.count为store.state.count
    ...mapState(['count', 'todos']),
    
    // 使用别名
    ...mapState({
      myCount: 'count',
      todoList: 'todos'
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2. Getters - 派生状态

Getters用于从store中的state派生出一些状态,类似于计算属性:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '学习Vue', done: true },
      { id: 2, text: '学习Vuex', done: false },
      { id: 3, text: '构建应用', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    },
    // 支持传参的getter
    getTodoById: state => id => {
      return state.todos.find(todo => todo.id === id)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在组件中使用:

<template>
  <div>
    <p>已完成任务: {{ doneTodosCount }}</p>
    <p>任务1: {{ todo1 }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    // 映射
    ...mapGetters(['doneTodos', 'doneTodosCount']),
    
    // 使用别名
    ...mapGetters({
      completedTasks: 'doneTodos'
    }),
    
    // 使用带参数的getter
    todo1() {
      return this.$store.getters.getTodoById(1)
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 3. Mutations - 状态变更

更改Vuex的store中的状态的唯一方法是提交mutation:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    // 简单变更
    increment(state) {
      state.count++
    },
    // 接收参数(载荷payload)
    incrementBy(state, n) {
      state.count += n
    },
    // 接收对象参数
    updateUser(state, userData) {
      state.user = { ...state.user, ...userData }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

提交mutation:

// 直接提交
store.commit('increment')

// 带参数
store.commit('incrementBy', 10)

// 对象风格提交
store.commit({
  type: 'incrementBy',
  amount: 10
})
1
2
3
4
5
6
7
8
9
10
11

在组件中使用:

import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations([
      'increment', // 映射this.increment()为this.$store.commit('increment')
      'incrementBy'
    ]),
    // 使用别名
    ...mapMutations({
      add: 'increment'
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意事项:

  • Mutation必须是同步函数
  • 尽量保持mutation简单、明确
  • 遵循Vue的响应规则(对象添加属性、数组项更改等)

# 4. Actions - 异步操作

Actions类似于mutations,不同在于:

  • Actions提交的是mutation,而不是直接变更状态
  • Actions可以包含任意异步操作
const store = new Vuex.Store({
  state: {
    todos: []
  },
  mutations: {
    setTodos(state, todos) {
      state.todos = todos
    },
    addTodo(state, todo) {
      state.todos.push(todo)
    }
  },
  actions: {
    // 简单action
    fetchTodos({ commit }) {
      return fetch('/api/todos')
        .then(response => response.json())
        .then(todos => {
          commit('setTodos', todos)
        })
    },
    
    // 使用async/await
    async fetchTodosAsync({ commit }) {
      try {
        const response = await fetch('/api/todos')
        const todos = await response.json()
        commit('setTodos', todos)
        return todos
      } catch (error) {
        console.error('获取任务失败:', error)
        throw error
      }
    },
    
    // 接收参数
    async addTodoAsync({ commit }, todoText) {
      try {
        const response = await fetch('/api/todos', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ text: todoText, done: false })
        })
        const newTodo = await response.json()
        commit('addTodo', newTodo)
        return newTodo
      } catch (error) {
        console.error('添加任务失败:', error)
        throw error
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

分发action:

// 分发
store.dispatch('fetchTodos')

// 带参数分发
store.dispatch('addTodoAsync', '学习Vuex')

// 对象风格分发
store.dispatch({
  type: 'addTodoAsync',
  text: '学习Vuex'
})
1
2
3
4
5
6
7
8
9
10
11

在组件中使用:

import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions([
      'fetchTodos', // 映射this.fetchTodos()为this.$store.dispatch('fetchTodos')
      'addTodoAsync'
    ]),
    // 使用别名
    ...mapActions({
      fetch: 'fetchTodos'
    }),
    // 处理action返回的Promise
    async loadData() {
      try {
        await this.fetchTodos()
        this.isLoaded = true
      } catch (error) {
        this.error = error.message
      }
    }
  },
  created() {
    this.loadData()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 5. Modules - 模块化状态管理

当应用变得复杂时,store对象可能变得相当臃肿。Vuex允许我们将store分割成模块:

// 用户模块
const user = {
  namespaced: true,
  state: {
    profile: null,
    token: null
  },
  getters: {
    isLoggedIn: state => !!state.token
  },
  mutations: {
    setProfile(state, profile) {
      state.profile = profile
    },
    setToken(state, token) {
      state.token = token
    }
  },
  actions: {
    async login({ commit }, credentials) {
      try {
        const { token, user } = await authService.login(credentials)
        commit('setToken', token)
        commit('setProfile', user)
        return user
      } catch (error) {
        throw error
      }
    },
    logout({ commit }) {
      commit('setToken', null)
      commit('setProfile', null)
    }
  }
}

// 商品模块
const products = {
  namespaced: true,
  state: {
    items: [],
    loading: false
  },
  getters: {
    availableProducts: state => state.items.filter(p => p.inventory > 0)
  },
  mutations: {
    setProducts(state, products) {
      state.items = products
    },
    setLoading(state, isLoading) {
      state.loading = isLoading
    }
  },
  actions: {
    async fetchProducts({ commit }) {
      commit('setLoading', true)
      try {
        const products = await productService.getProducts()
        commit('setProducts', products)
      } finally {
        commit('setLoading', false)
      }
    }
  }
}

// 根store
const store = new Vuex.Store({
  modules: {
    user,
    products
  }
})

// 访问模块状态
console.log(store.state.user.profile) // 用户信息
console.log(store.state.products.items) // 商品列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

在组件中使用命名空间模块:

import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // 使用命名空间访问
    ...mapState('user', ['profile']),
    ...mapGetters('user', ['isLoggedIn']),
    ...mapState('products', {
      productList: 'items',
      isLoading: 'loading'
    }),
    ...mapGetters('products', ['availableProducts'])
  },
  methods: {
    ...mapActions('user', ['login', 'logout']),
    ...mapActions('products', ['fetchProducts']),
    
    // 或者直接通过store访问
    loginUser() {
      this.$store.dispatch('user/login', {
        username: this.username,
        password: this.password
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Vuex插件与高级特性

# 插件

Vuex插件就是一个函数,接收store作为唯一参数:

// 简单的日志插件
const logPlugin = store => {
  // 在每个mutation完成后调用
  store.subscribe((mutation, state) => {
    console.log('mutation:', mutation.type, mutation.payload)
    console.log('new state:', state)
  })
}

// 持久化插件
const persistPlugin = store => {
  // 从localStorage恢复状态
  const savedState = localStorage.getItem('vuex-state')
  if (savedState) {
    store.replaceState(JSON.parse(savedState))
  }
  
  // 订阅状态变更
  store.subscribe((mutation, state) => {
    localStorage.setItem('vuex-state', JSON.stringify(state))
  })
}

// 使用插件
const store = new Vuex.Store({
  // ...
  plugins: [logPlugin, persistPlugin]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 严格模式

严格模式下,任何非mutation方式修改Vuex状态都会抛出错误:

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

注意: 不要在生产环境启用严格模式,会影响性能。

# Vuex与Vue组合式API

在Vue 3的组合式API中使用Vuex:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ double }}</p>
    <button @click="increment">+</button>
    <button @click="fetchData">Load Data</button>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    // 获取状态
    const count = computed(() => store.state.count)
    
    // 获取getters
    const double = computed(() => store.getters.doubleCount)
    
    // 方法
    const increment = () => store.commit('increment')
    const fetchData = () => store.dispatch('fetchData')
    
    return {
      count,
      double,
      increment,
      fetchData
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 实际应用示例

让我们构建一个包含身份验证和产品管理的电子商务应用的Vuex store:

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

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    auth,
    products,
    cart
  },
  state: {
    notificationMessage: null,
    isLoading: false
  },
  mutations: {
    setNotification(state, message) {
      state.notificationMessage = message
    },
    clearNotification(state) {
      state.notificationMessage = null
    },
    setLoading(state, status) {
      state.isLoading = status
    }
  },
  actions: {
    notify({ commit }, message) {
      commit('setNotification', message)
      setTimeout(() => {
        commit('clearNotification')
      }, 3000)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// store/modules/auth.js
import authApi from '@/api/auth'

export default {
  namespaced: true,
  state: {
    user: null,
    token: localStorage.getItem('token') || null
  },
  getters: {
    isAuthenticated: state => !!state.token,
    currentUser: state => state.user
  },
  mutations: {
    setToken(state, token) {
      state.token = token
      if (token) {
        localStorage.setItem('token', token)
      } else {
        localStorage.removeItem('token')
      }
    },
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
    async login({ commit, dispatch }, credentials) {
      try {
        const { token, user } = await authApi.login(credentials)
        commit('setToken', token)
        commit('setUser', user)
        dispatch('notify', '登录成功', { root: true })
        return user
      } catch (error) {
        dispatch('notify', `登录失败: ${error.message}`, { root: true })
        throw error
      }
    },
    async register({ commit, dispatch }, userData) {
      try {
        const { token, user } = await authApi.register(userData)
        commit('setToken', token)
        commit('setUser', user)
        dispatch('notify', '注册成功', { root: true })
        return user
      } catch (error) {
        dispatch('notify', `注册失败: ${error.message}`, { root: true })
        throw error
      }
    },
    async fetchUserProfile({ commit, state, dispatch }) {
      if (!state.token) return null
      
      try {
        const user = await authApi.getProfile()
        commit('setUser', user)
        return user
      } catch (error) {
        dispatch('notify', `获取用户信息失败: ${error.message}`, { root: true })
        if (error.status === 401) {
          dispatch('logout')
        }
        throw error
      }
    },
    logout({ commit }) {
      commit('setToken', null)
      commit('setUser', null)
      commit('cart/clearCart', null, { root: true })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

使用示例:

<template>
  <div class="login-form">
    <h2>用户登录</h2>
    <div v-if="error" class="error">{{ error }}</div>
    
    <form @submit.prevent="handleLogin">
      <div class="form-group">
        <label for="email">邮箱</label>
        <input 
          id="email"
          v-model="email"
          type="email"
          required
        />
      </div>
      
      <div class="form-group">
        <label for="password">密码</label>
        <input 
          id="password"
          v-model="password"
          type="password"
          required
        />
      </div>
      
      <button 
        type="submit" 
        :disabled="isLoading"
      >
        {{ isLoading ? '登录中...' : '登录' }}
      </button>
    </form>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
  data() {
    return {
      email: '',
      password: '',
      error: null
    }
  },
  computed: {
    ...mapState({
      isLoading: state => state.isLoading
    })
  },
  methods: {
    ...mapActions('auth', ['login']),
    
    async handleLogin() {
      this.error = null
      try {
        await this.login({
          email: this.email,
          password: this.password
        })
        this.$router.push('/dashboard')
      } catch (err) {
        this.error = err.message || '登录失败'
      }
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# 性能优化

随着应用规模增长,Vuex store可能变得庞大,影响性能。以下是一些优化策略:

  1. 模块动态注册 - 按需加载模块
  2. 规范化状态 - 避免重复和嵌套数据
  3. 选择性订阅 - 只监听需要的状态
  4. 计算属性缓存 - 利用Vue的计算属性缓存
// 动态注册模块
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 动态注册auth模块
    if (!store.hasModule('auth')) {
      store.registerModule('auth', authModule)
    }
  }
  next()
})
1
2
3
4
5
6
7
8
9
10

# 与Composition API结合

Vue 3的组合式API提供了更灵活的方式使用Vuex:

<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()

// 获取state
const count = computed(() => store.state.count)

// 获取getters
const doubleCount = computed(() => store.getters.doubleCount)

// 提交mutation
const increment = () => store.commit('increment')

// 分发action
const fetchData = () => store.dispatch('fetchData')
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 从Vuex迁移到Pinia

随着Vue 3的普及,官方推荐的状态管理方案已从Vuex转向Pinia。Pinia被视为Vuex 5的雏形,提供了更简单的API、更好的TypeScript支持和更轻量的包体积。

# Pinia与Vuex的主要区别

  1. 更简单的API: 没有mutations,只有state、getters和actions
  2. 更好的TypeScript支持: 自动类型推导,无需额外类型声明
  3. 去除命名空间: 使用store实例直接导入和使用
  4. DevTools支持: 改进的开发者工具体验

# 简单迁移示例

// Vuex store
export default {
  namespaced: true,
  state: {
    counter: 0
  },
  getters: {
    doubleCount: (state) => state.counter * 2
  },
  mutations: {
    increment(state) {
      state.counter++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit('increment'), 1000)
    }
  }
}

// 同等功能的Pinia store
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCount: (state) => state.counter * 2
  },
  actions: {
    increment() {
      this.counter++
    },
    incrementAsync() {
      setTimeout(() => this.increment(), 1000)
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 学习资源

# 结语

Vuex是Vue生态系统中不可或缺的部分,为复杂应用提供了可靠的状态管理方案。随着对其概念的理解和实践,您将能够构建更加健壮和可维护的Vue应用。在我在字节跳动的工作经验中,正确使用Vuex使我们能够轻松管理复杂应用中的共享状态,显著提升了团队协作效率和代码质量。

Last Updated: 2025-03-19T01:09:41.000Z