Action
类似于mutation
,不同在于:
Action
提交的是 mutation
,而不是直接变更状态。Action
可以包含任意异步操作。让我们来注册一个简单的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
// context = {dispatch, commit, getters, state, rootGetters, rootState}
increment (context) {
context.commit('increment')
}
}
})
从这里可以看出,context
有6个属性分别是dispatch, commit, getters, state, rootGetters, rootState
。你也许会觉得奇怪,为什么getters
与state
有各自的root
信息,而dispatch
与commit
没有?
因为:dispatch
, commit
是方法,可以通过传递参数指向对应的root
方法,即store.dispatch(type, payload, {root: true})
;而getters
与state
是属性,不能传参,所以重新引入了rootGetters
, rootState
属性
和mutations
一样,核心方法都在 installModule
方法中。
export class Store {
constructor (options = {}) {
...
// 生成state模型
this._modules = new ModuleCollection(options)
const state = this._modules.root.state
// 处理[state, getters, mutations, actions]的核心方法
installModule(this, state, [], this._modules.root)
...
}
}
这段代码主要是将原始 actions 对象封装到store上
function installModule (store, rootState, path, module, hot) {
const namespace = store._modules.getNamespace(path)
// 生成本地local对象
const local = module.context = makeLocalContext(store, namespace, path)
// 关键代码:
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
核心是 module.forEachAction
,接下来对其进行展开
从这里可以看出,我们写的actions
方法,可以接受两个参数,第一个是context = {dispatch, commit, getters, state, rootGetters, rootState}
,第二个是额外自定义参数payload
。并且this
指向全局store
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 将其展开如下
Object.keys(originActions).forEach(key => {
const type = originActions[key].root ? key : namespace + key
const handler = originActions[key].handler || originActions[key]
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload) {
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
// 将结果封装成Promise返回
if (!isPromise(res)) {
res = Promise.resolve(res)
}
return res
})
}
从这可以看出,actions
可以写方法,也能写对象,以下两种写法效果是一样的
{
actions: {
increment (context) {
context.commit('increment')
}
}
}
{
actions: {
increment: {
root: false,
handler (context) {
context.commit('increment')
}
}
}
}
在返回res
之前,会判断res
是否为Promise
,如果不是,则用Promise
进行封装返回。
接下来分析makeLocalContext
方法对actions的封装
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
// 从这里可以看出,local.dispatch 分两种情况
// 1. 如果不是子模块,使用全局的 dispatch
// 2. 如果是子模块,若!options.root===true,则自动给dispatch的type加上前缀,即默认为本地dispatch,且不用自己写前缀
// local.commit同理
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
...
if (!options || !options.root) {
type = namespace + type
}
store.commit(type, payload, options)
}
}
Object.defineProperties(local, {
getters: { get: () => {} },
state: { get: () => {} }
})
return local
}
export class Store {
constructor (options = {}) {
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
}
dispatch(_type, _payload) {
const { type, payload } = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
// 执行action的before订阅
this._actionSubscribers.slice()
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
// 执行dispatch
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
// 执行action的after订阅
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
resolve(res)
})
})
}
}
Store
的actions
有两种写法actions
方法传入的参数context
有6个属性,context = {dispatch, commit, getters, state, rootGetters, rootState}
dispatch, commit
默认是局部的,可以传入dispatch(type, {root: true})
指向全局dispatch
store.subscribeAction(fn, options)
进行订阅,在执行dispatch
前后,执行指定方法。fn = {brfore:fn1,after:fn2}
,如果fn
是一个方法,代表before