Action 类似于 Mutation,不同之处在于:
- Action 提交的是 Mutation,而不是直接变更状态
- Action 可以包含任意异步操作
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 Mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
实践中,我们会经常用到 ES2015 的参数解构来简化代码:
actions: {
increment({ commit }) {
commit('increment')
}
}
Action 通过 store.dispatch 方法触发:
store.dispatch('increment')
乍一眼看上去感觉多此一举,我们为何不直接分发 Mutation 呢?实际上并非如此,还记得 Mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 Action 内部执行异步操作:
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Actions 支持同样的载荷方式和对象方式进行分发:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
export default {
methods: {
increment() {
this.$store.dispatch('increment')
}
}
}
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment',
'incrementBy'
]),
...mapActions({
add: 'increment'
})
}
}
Action 通常是异步的,那么如何知道 Action 什么时候结束呢?更重要的是,我们如何才能组合多个 Action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch 可以处理被触发的 Action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:
actions: {
actionA({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
现在你可以:
store.dispatch('actionA').then(() => {
// ...
})
在另外一个 Action 中也可以:
actions: {
actionB({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
如果我们利用 async / await,可以如下组合 Action:
actions: {
async actionA({ commit }) {
commit('gotData', await getData())
},
async actionB({ dispatch, commit }) {
await dispatch('actionA')
commit('gotOtherData', await getOtherData())
}
}
一个 store.dispatch 在不同模块中可以触发多个 Action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
const store = new Vuex.Store({
state: {
user: null,
token: null,
loading: false,
error: null
},
mutations: {
SET_USER(state, user) {
state.user = user
},
SET_TOKEN(state, token) {
state.token = token
},
SET_LOADING(state, loading) {
state.loading = loading
},
SET_ERROR(state, error) {
state.error = error
}
},
actions: {
async login({ commit }, credentials) {
commit('SET_LOADING', true)
commit('SET_ERROR', null)
try {
const response = await api.login(credentials)
commit('SET_USER', response.user)
commit('SET_TOKEN', response.token)
localStorage.setItem('token', response.token)
return response
} catch (error) {
commit('SET_ERROR', error.message)
throw error
} finally {
commit('SET_LOADING', false)
}
},
async logout({ commit }) {
try {
await api.logout()
} finally {
commit('SET_USER', null)
commit('SET_TOKEN', null)
localStorage.removeItem('token')
}
}
}
})
actions: {
async fetchPosts({ commit, state }, { page = 1 } = {}) {
if (state.postsLoading) return
commit('SET_POSTS_LOADING', true)
try {
const response = await api.getPosts({ page })
commit('SET_POSTS', response.data)
commit('SET_PAGINATION', response.pagination)
} catch (error) {
commit('SET_POSTS_ERROR', error.message)
} finally {
commit('SET_POSTS_LOADING', false)
}
}
}
actions: {
async addToCart({ commit, state }, product) {
const existingItem = state.cart.find(item => item.id === product.id)
if (existingItem) {
commit('UPDATE_CART_ITEM_QUANTITY', {
id: product.id,
quantity: existingItem.quantity + 1
})
} else {
commit('ADD_CART_ITEM', {
...product,
quantity: 1
})
}
try {
await api.syncCart(state.cart)
} catch (error) {
console.error('同步购物车失败', error)
}
}
}
| 特性 | Mutation | Action |
|---|---|---|
| 作用 | 直接修改状态 | 提交 Mutation |
| 异步 | 不支持 | 支持 |
| 调用方式 | commit | dispatch |
| 调试 | 容易追踪 | 需要额外处理 |
export const ACTION_TYPES = {
FETCH_USER: 'fetchUser',
FETCH_POSTS: 'fetchPosts',
CREATE_POST: 'createPost',
UPDATE_POST: 'updatePost',
DELETE_POST: 'deletePost'
}
actions: {
async fetchData({ commit }) {
commit('SET_LOADING', true)
try {
const data = await api.fetchData()
commit('SET_DATA', data)
return { success: true, data }
} catch (error) {
commit('SET_ERROR', error.message)
return { success: false, error: error.message }
} finally {
commit('SET_LOADING', false)
}
}
}
let searchTimer = null
actions: {
searchProducts({ commit }, keyword) {
if (searchTimer) {
clearTimeout(searchTimer)
}
searchTimer = setTimeout(async () => {
commit('SET_SEARCH_LOADING', true)
try {
const results = await api.searchProducts(keyword)
commit('SET_SEARCH_RESULTS', results)
} finally {
commit('SET_SEARCH_LOADING', false)
}
}, 300)
}
}
let pendingRequest = null
actions: {
async fetchUsers({ commit }, params) {
if (pendingRequest) {
pendingRequest.cancel('请求已取消')
}
const cancelToken = axios.CancelToken.source()
pendingRequest = cancelToken
try {
const response = await api.getUsers(params, {
cancelToken: cancelToken.token
})
commit('SET_USERS', response.data)
} catch (error) {
if (!axios.isCancel(error)) {
commit('SET_ERROR', error.message)
}
} finally {
pendingRequest = null
}
}
}