Vue.js - 使用Vuex进行状态管理详解5(actions执行异步操作)
作者:hangge | 2026-10-06 07:04
在 Vuex 状态管理模式中,actions 是一个非常重要的概念。与 mutations 不同,actions 用于处理异步操作。下面我将详细介绍 actions 的基本使用、参数传递、辅助函数以及返回 Promise 对象等内容。



一、基本介绍
1,什么是 actions?
(1)actions 类似于 mutations,但是有以下两个关键区别:
- Actions 不直接变更状态:而是通过提交 mutation 来间接改变状态
- Actions 可以包含异步操作:而 mutations 必须是同步函数
(2)为什么要使用 actions?
- 当需要执行异步操作(如 API 请求)后再更新状态时,必须使用 actions
- mutations 必须是同步的,这样才能在 Vue DevTools 中追踪状态变化的前后快照
- 如果在 mutation 中执行异步操作,将无法正确追踪状态变化
提示:Vuex 官方推荐的最佳实践是组件不应该直接提交 mutations,而是通过分发 actions 来间接提交 mutations。
2,actions 的基本结构
(1)一个 action 函数接收一个 context 对象作为参数,这个对象包含以下属性和方法:
- context.commit:提交 mutation
- context.dispatch:分发其他 action
- context.state:获取 store 的状态
- context.getters:获取 getters
- context.rootState:获取根状态(在模块中使用)
- context.rootGetters:获取根 getters(在模块中使用)
(2)context 参数支持 ES6 解构写法,这在实际开发中非常常用:
actions: {
incrementAction ({ commit }) {
commit('increment')
}
}
二、actions 的基本使用
1,定义 actions
(1)下面我们通过一个计数器案例来演示 actions 的基本使用。首先修改 src/store/index.js 文件,在 actions 中添加两个 action 函数:
- 在 incrementAction 函数中使用了 setTimeout 来模拟异步操作(如网络请求)。
- 在 decrementAction 函数中展示了如何通过 ES6 解构获取 context 的各个属性。
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
counter: 0
}
},
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
getters: {
doubleCounter(state) {
return state.counter * 2
}
},
// actions 的基本使用
actions: {
incrementAction(context) {
// 使用 setTimeout 模拟异步操作
setTimeout(() => {
context.commit('increment') // 提交 mutation
}, 1000)
},
decrementAction(context) {
// ES6 解构 context 对象
let { commit, dispatch, state, rootState, getters, rootGetters } = context
commit('decrement') // 提交 mutation
}
}
})
export default store
(2)注意:actions 中也可以提交多个 mutation:
actions: {
incrementAction({ commit, dispatch }) {
commit('increment') // 提交第一个 mutation
commit('anotherMutation') // 提交第二个 mutation
dispatch('otherAction') // 还可以分发其他 action
}
}
2,在组件中分发 actions
(1)创建一个新的组件文件,通过两种方式分发 actions:
<template>
<div>
<h4>当前计数: {{ $store.state.counter }}</h4>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
methods: {
increment() {
// Options API 语法:使用 this.$store.dispatch
this.$store.dispatch('incrementAction')
}
},
setup() {
// Composition API 语法:使用 useStore
const store = useStore()
const decrement = () => {
store.dispatch('decrementAction')
}
return { decrement }
}
}
</script>
(2)运行效果如下:

三、actions 接收参数
1,payload 参数传递
(1)与 mutations 类似,actions 定义的函数也可以接收两个参数:第一个是 context,第二个是 payload(用于接收分发时传递的参数)。
提示:payload 可以是任何类型的数据,但通常建议使用对象形式,这样可以传递多个参数。
(2)下面我们为计数器添加一个 "+10" 的功能。首先在组件中添加按钮和分发逻辑:
<template>
<div>
<h4>当前计数: {{ $store.state.counter }}</h4>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="addTen">+10</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const increment = () => {
store.dispatch('incrementAction')
}
const decrement = () => {
store.dispatch('decrementAction')
}
const addTen = () => {
// 分发 action,并传递对象参数
store.dispatch('incrementNAction', { num: 10 })
}
return { increment, decrement, addTen }
}
}
</script>
(3)然后在 store 中定义对应的 action 和 mutation:
import { createStore } from 'vuex'
// 常量形式的 mutation 类型
const INCREMENT_N = 'INCREMENT_N'
const store = createStore({
state() {
return {
counter: 0
}
},
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
},
INCREMENT_N(state, payload) {
state.counter += payload.num
}
},
actions: {
incrementNAction(context, payload) {
// payload 接收 dispatch 传递过来的参数
// payload 值为 { num: 10 }
context.commit(INCREMENT_N, payload)
}
}
})
export default store
(4)运行效果如下:

2,对象风格分发
store.dispatch 还支持对象风格的分发语法:
提示:使用对象风格分发时,整个对象会作为 payload 传递给 action 函数。
// 普通分发方式
store.dispatch('incrementNAction', { num: 10 })
// 上面的写法等同于下面的对象风格写法
store.dispatch({
type: 'incrementNAction',
num: 10
})
四、mapActions 辅助函数
1,什么是 mapActions?
(1)Vuex 提供了 mapActions 辅助函数,用于将组件的 methods 映射为 store.dispatch 调用,从而简化代码编写。
(2)mapActions 支持两种映射方式:
- 数组方式:直接映射同名方法
- 对象方式:可以重命名映射的方法名
2,Options API 中使用 mapActions
在 Options API 中,mapActions 直接映射到 methods 选项:
提示:使用对象方式时,如果需要传递参数,可以在模板中直接调用时传入,如 addTen({ num: 20 })。
<template>
<div>
<h4>当前计数: {{ $store.state.counter }}</h4>
<!-- 点击按钮会调用映射好的 action -->
<button @click="incrementAction">+1</button>
<button @click="decrement">-1</button>
<button @click="addTen({ num: 20 })">+20</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
// 数组方式:将 this.incrementAction() 映射为 this.$store.dispatch('incrementAction')
...mapActions(['incrementAction']),
// 对象方式:可以重命名
...mapActions({
decrement: 'decrementAction', // 将 this.decrement() 映射为 dispatch('decrementAction')
addTen: 'incrementNAction' // 将this.addTen(payload)映射为dispatch('incrementNAction', payload)
})
}
}
</script>
3,Composition API 中使用 mapActions
(1)在 Composition API 的 setup 函数中,也可以使用 mapActions:
<script>
import { mapActions, useStore } from 'vuex'
export default {
setup() {
const store = useStore()
// mapActions 返回一个对象,包含映射好的方法
const actionsFuncs = mapActions({
decrement: 'decrementAction',
addTen: 'incrementNAction'
})
// 将映射好的方法解构返回
return {
incrementAction: () => store.dispatch('incrementAction'),
...actionsFuncs
}
}
}
</script>
(2)更推荐的方式是直接使用 useStore:
<script setup>
import { useStore } from 'vuex'
const store = useStore()
// 直接定义分发 action 的方法
const incrementAction = () => store.dispatch('incrementAction')
const decrementAction = () => store.dispatch('decrementAction')
const incrementNAction = (payload) => store.dispatch('incrementNAction', payload)
</script>
五、action 返回 Promise 对象
1,为什么需要返回 Promise?
(1)action 通常用于处理异步操作。如果我们想知道 action何时执行完成,可以让 action 函数返回一个 Promise 对象。
(2)实际上,store.dispatch 默认就会返回 action 函数返回的 Promise,这样我们就可以在分发后使用 .then() 或 async/await 来处理异步结果。
2,实战案例:获取网络数据
(1)下面通过一个获取 UUID 的案例来演示如何让 action 返回 Promise。首先修改 store 定义:
提示:https://httpbin.org 是一个测试 HTTP 请求和响应的免费服务,非常适合用于学习和测试。
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
uuid: null
}
},
mutations: {
addUUID(state, payload) {
state.uuid = payload // 存储网络获取到的 uuid
}
},
getters: {
// getters 定义...
},
actions: {
// 编写一个获取 uuid 的 action
getUUIDAction({ commit }) {
// 直接返回 Promise 对象,作为 dispatch 函数的返回值
return new Promise((resolve, reject) => {
// 发起网络请求(这是一个免费获取 uuid 的接口)
fetch('https://httpbin.org/uuid')
// 将响应解析为 JSON 格式
.then(res => res.json())
.then(data => {
// 将请求获取的数据存到 Vuex 中
commit('addUUID', data.uuid)
// 调用 resolve 完成异步操作
resolve(data)
})
.catch(err => {
// 错误处理
reject(err)
})
})
}
}
})
export default store
(2)在组件中使用这个 action:
<template>
<div>
<h4>uuid: {{ $store.state.uuid }}</h4>
</div>
</template>
<script>
import { onMounted } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
onMounted(() => {
// 分发 action,返回一个 Promise 对象
const promise = store.dispatch('getUUIDAction')
// 使用 then 处理异步请求成功的结果
promise.then(res => {
console.log('请求成功:', res)
}).catch(err => {
console.log('请求失败:', err)
})
})
}
}
</script>
(3)运行效果:

3,使用 async/await 简化代码
(1)我们可以使用 async/await 语法让代码更加简洁清晰。首先修改 store 定义:
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
uuid: null,
}
},
mutations: {
addUUID(state, payload) {
state.uuid = payload // 存储网络获取到的 uuid
},
},
getters: {
// getters 定义...
},
actions: {
// 编写一个获取 uuid 的 action
// 使用 async/await 语法
async getUUIDAction({ commit }) {
try {
// 发起网络请求
const response = await fetch('https://httpbin.org/uuid')
const data = await response.json()
// 提交 mutation 更新状态
commit('addUUID', data.uuid)
// 返回数据
return data
} catch (err) {
// 错误处理
console.error('获取 UUID 失败:', err)
throw err
}
},
},
})
export default store
(2)在组件中使用:
<template>
<div>
<h4>uuid: {{ $store.state.uuid }}</h4>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
onMounted(async () => {
try {
// 使用 await 等待 action 完成
const result = await store.dispatch('getUUIDAction')
console.log('请求成功:', result)
} catch (err) {
console.log('请求失败:', err)
}
})
</script>
4,组合多个 actions
(1)有时候我们需要在一个 action 中调用另一个 action,并等待其完成。这可以通过返回 Promise 来实现:
actions: {
actionA({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
},
actionB({ dispatch, commit }) {
// 先等待 actionA 完成,再执行其他操作
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
(2)使用 async/await 可以让组合 actions 更加直观:
提示:一个 store.dispatch 可以在不同模块中触发多个 action 函数。在这种情况下,返回的 Promise 只有当所有触发的函数都完成后才会完成。
actions: {
async actionA({ commit }) {
commit('gotData', await getData())
},
async actionB({ dispatch, commit }) {
// 等待 actionA 完成
await dispatch('actionA')
// 然后执行其他操作
commit('gotOtherData', await getOtherData())
}
}
附:最佳实践与常见问题
1,actions 与 mutations 的职责划分
遵循以下职责划分原则可以让代码更加清晰:
- Mutations:只负责同步更新状态,每次提交只做一件事
- Actions:负责异步操作、提交 mutations、分发其他 actions
- 组件:只分发 actions,不直接提交 mutations
注意:不要在 mutations 中执行异步操作。这会导致 Vue DevTools 无法正确追踪状态变化,也会使调试变得困难。
2,常量形式的 mutation 类型
(1)在大型项目中,建议使用常量代替 mutation 类型字符串,这样可以利用 ESLint 等工具进行代码检查,同时让代码更清晰:
// mutation-types.js export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT' export const INCREMENT_N = 'INCREMENT_N'
(2)在 store 中使用:
import { INCREMENT, DECREMENT } from './mutation-types'
const store = createStore({
mutations: {
[INCREMENT](state) {
state.counter++
},
[DECREMENT](state) {
state.counter--
}
},
actions: {
incrementAction({ commit }) {
commit(INCREMENT)
}
}
})
3,常见的错误处理方式
(1)在 action 中进行网络请求时,务必做好错误处理:
actions: {
async fetchDataAction({ commit }) {
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error('请求失败')
}
const data = await response.json()
commit('setData', data)
return data
} catch (err) {
// 可以提交一个错误状态的 mutation
commit('setError', err.message)
throw err // 继续抛出错误,让组件也能处理
}
}
}
(2)在组件中处理错误:
<script setup>
import { useStore } from 'vuex'
const store = useStore()
const loadData = async () => {
try {
await store.dispatch('fetchDataAction')
} catch (err) {
// 在组件中处理错误,如显示提示信息
alert('加载失败: ' + err.message)
}
}
</script>
全部评论(0)