我们知道在vuex中,通过提交commit,触发mutations中的方法,来改变state中的状态。Mutation必须是同步函数,异步方法需要写在Action中,在Action中提交commit来改变state的状态,这点在Mutation | Vuex (vuejs.org)中都有说明。
Mutation是同步函数,这点没什么疑问。比如有一个store:
export default new Vuex.Store({
state:{
someWords: 'default',
},
mutations:{
UPDATE_SOME_WORDS(state, someWords){
state.someWords = someWords;
},
},
actions:{
updateSomeWords({commit}, someWords){
commit('UPDATE_SOME_WORDS', someWords);
},
},
})
对于下面一段代码:
console.log(store.state.someWords);
store.commit('UPDATE_SOME_WORDS', 'updated');
console.log(store.state.someWords);
应该输出default updated
,因为mutation是同步的,提交commit后state立马会改变。
我们看到官网文档中说,Action通常是异步的,并且Action可以返回Promise,因此可以这么写:store.dispatch('actionA').then(()=>{//...})
。那么action中的方法,本身是同步执行还是异步执行的呢?对于下面一段代码:
console.log(store.state.someWords);
store.dispatch('updateSomeWords', 'updated');
console.log(store.state.someWords);
输出为什么是default updated
呢?Action不应该是异步的吗?
了解这个问题之前,首先需要看一下关于Promise的一些问题:ES6 Promise。之后来看看vuex源码store.js - vuejs/vuex:
Store构造函数中会初始化一个私有属性_actions:
this._actions = Object.create(null) // 创建一个没有继承原型方法的空对象
之后会执行installModule方法:
function installModule (store, rootState, path, module, hot) {
// 安装根Module
// ...
module.forEachAction((action, key) => { // 遍历Module中的action,对每个action执行该匿名函数,参数为:action函数,函数名
const type = action.root ? key : namespace + key // 根Module ? 函数名 : 命名空间 + 函数名
const handler = action.handler || action // 获取相应action函数
registerAction(store, type, handler, local) // 注册action
})
// 安装Store中每个Module
// ...
}
registerAction方法中会注册每个action:
function registerAction (store, type, handler, local) { // 参数:store实例,action名,action函数,上下文
const entry = store._actions[type] || (store._actions[type] = []) // _actions中没[type]属性,则初始化该属性为空数组
entry.push(function wrappedActionHandler (payload) { // 往_actions中push该函数
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload) // 执行该action函数,上下文为store,参数为:一个与store实例有相同方法和属性的对象,payload
if (!isPromise(res)) { // 如果res不是Promise对象,则包装成fulfilled的Promise对象
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res // 返回action执行结果,为一个Promise
}
})
}
当我们调用store.dispatch
时,触发dispatch方法:
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // dispatch参数可以是(type, payload),也可以是({type, payload}),这里规整成一个统一的对象
const action = { type, payload } // type为action名,payload为参数
const entry = this._actions[type] // 这里的entry是一个函数,即registerAction中push的函数
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload) // 重点在于这一步。如果有多个同名action函数,则构造成Promise.all对象,否则直接去执行entry中的函数,也即action函数。我们知道,Promise构造函数参数函数是会立即执行的,所以对于action中全是同步方法的action,会立即执行,不会去生成异步方法。
return new Promise((resolve, reject) => { // 返回Promise,当result的Promise resolve()或reject()后,该Promise resolve()或reject()
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
因此,当action中只有同步方法时,dispatch操作实际上也是同步的,当action中有异步方法时,异步方法的then或catch会异步执行。
网友评论