这是对vuex3.x版本的源码分析。
本次分析会按以下方法进行:
- 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升`,甚至面试时的回答都非常有帮助。
- 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
- 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。
本章只讲解vuex中的Action,这也是vuex官网中“核心概念”的第四个。
想了解vuex中的其他源码分析,欢迎参考我发布的下列文章:
VUEX 3 源码分析——1. 理解State
VUEX 3 源码分析——2. 理解Getter
VUEX 3 源码分析——3. 理解Mutations
VUEX3 源码分析——4. 理解Action
VUEX 3 源码分析——5. 理解Module
VUEX 3 源码分析——6. 理解命名空间namespace
VUEX 3 源码分析——7. 模块的动态注册和卸载
官网示例
- 先看一下官网示例:
- 在actions上定义函数,函数接受一个context对象,这个对象可以调用commit,state和getters。(官方说context对象不是store实例本身,等会看内部的实现源码就能更直观地感受到了)
- action通过dispatch方法触发
- 辅助函数 mapActions
- 如果你看过我之前分析的两篇文章(理解Getter 和 理解Mutations),就能发现action的功能和getter,尤其是mutation大同小异,只是action能进行异步操作嘛。那JavaScript里面,哪种语法适合异步操作能——当然是Promise啦。
- 所以通过深入分析开源项目中的特定功能,我们可以洞察到项目背后的统一设计思想。
- 理解这一设计思想不仅有助于我们掌握单个功能的具体实现,还能帮助我们预测和理解其他功能的设计模式。
- 此外,阅读和学习优秀的开源项目,能够显著提升我们的代码理解能力和审美水平,从而在软件开发实践中,更好地应用这些设计原则和模式。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
store.dispatch('increment')
初始化Actions
- 之前的Getter和Mutation初始化工作,都是在installModule函数中执行的,action也必然在此。
- Store类初始化一个无原型链对象 this_actions:this._actions = Object.create(null)
- 将用户定义的action按key-value格式,存储进 this._actions[key] 中,其中的每个key对应一个数组,数组中的元素为函数,每个函数都返回一个执行action的Promise
// store-util.js
export function installModule(store, rootState, path, module, hot) {
...
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key;
const handler = action.handler || action;
registerAction(store, type, handler, local)
})
}
function registerAction(store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler(payload) {
const store_obj = {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootSatte: store.state
} // 这就是官方手册说的action函数接受的context对象不是store实例本身
let res = handler.call(store, store_obj, payload)
// 如果res不是promise,将被封装在一个Promise对象中
if (!isPromise(res)) {
res = Promise.resolve(res)
}
return res
})
}
// ./module/module.js
export default class Module {
...
forEachAction(fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
}
实现dispatch方法
- 在Store类上定义dispatch方法。将传入的参数格式化后,以Promise.all的方式调用 this._actions[type]中的所有promise,返回一个新的Promise。
- 另外这里我想对第8行的相关代码(const result = entry.length > 1 的三元判断内容)做一点个人分析。
- 当时看的时候,觉得这段代码很冗长。觉得 const result = Promise.all(entry.map(handler => handler(payload))) 这样写会不会更简洁明了,为什么非要对entry的不同长度来做不同处理。
- 但是这样写更体现开源项目的设计思想。在一个元素的情况下(entry.length === 1),直接调用这个元素,会比启动一个all的调用更有效率。虽然在大多数情况下,这种额外开销都是无感知的。但是作为开源项目,是不清楚使用者的实际业务场景的,所以需要尽可能地进行全面考虑。如果有大量操作或高负载的情况下,这种性能优化就会很明显。
// ./store.js
export class Store {
...
dispatch(_type, _payload) {
// unifyObjectStyle函数让 Actions 支持同样的载荷方式和对象方式进行分发
const {type, payload} = unifyObjectStyle(_type, _payload)
const action = {type, payload} // 分发 sub action 用的
...
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
... // 插件的处理
resolve(res)
}, error => {
... // 插件的处理
reject(error)
})
})
}
}
// ./store-util.js
export function unifyObjectStyle (type, payload, options) {
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}
- 最后将dispatch方法以类似隔离的方式,绑定store为dispatch 的指定上下文。
- 确保在不同的执行环境中调用dispatch时,它总是与创建它的store实例相关联。
// ./store.js
export class Store {
const store = this;
const { dispatch, commit } = this;
this.dispatch = function boundDispatch(type, payload) {
return dispatch.call(store, type, payload)
}
}
mapActions 辅助函数
- 实现方式和mapMutations一模一样,只是将commit换成了dispatch
// ./helper.js
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
...
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
// get dispatch function from store
let dispatch = this.$store.dispatch
...
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
以上就是官网上Action的相关源码,下一遍会分析Module的实现源码。