Vuex源码只有一千行,主要使用了Store类、ModuleCollection类、Module类,结构清晰。下面简单介绍一下Vuex中的一些主要源码实现。推荐打开Vuex源码一起观看。?vuex使用方法Vue.use(Vuex)conststore=newVuex.Store({state:{count:0},mutations:{increment(state){state.count++}}})newVue({store,...})根据使用方法,我们依次看看vuex做了什么。?Vue.use(Vuex)首先加载Vuex插件,Vue.use方法会调用插件传入的install方法。在源码src/index.js中找到了import{Store,install}from'./store'的安装方法。install函数调用核心方法applyMixin(Vue)。applyMixin位于src/mixin.js文件和exportdefaultfunction(Vue){constversion=Number(Vue.version.split('.')[0])if(version>=2){Vue.mixin({beforeCreate:vuexInit})}else{...}functionvuexInit(){constoptions=this.$options//store注入if(options.store){this.$store=typeofoptions.store==='function'?options.store():options.store}elseif(options.parent&&options.parent.$store){this.$store=options.parent.$store}}}它的核心功能是挂载我们传递的store对象in到后代的每个Vue实例,这样我们就可以通过this.$store访问商店对象。?newVuex.Store()接下来是核心部分,实例化Store。看看构造函数运行时会发生什么。...this._committing=falsethis._actions=Object.create(null)this._actionSubscribers=[]this._mutations=Object.create(null)this._wrappedGetters=Object.create(null)this._modules=newModuleCollection(options)this._modulesNamespaceMap=Object.create(null)this._subscribers=[]this._watcherVM=newVue()this._makeLocalGettersCache=Object.create(null)...先定义一堆属性,关键一个行是this._modules=newModuleCollection(options)。ModuleCollection类是模块管理中心,负责注册、删除、根模块识别等简单功能。options是实例化Store时传入的参数。模块的定义如下。由于使用了单一的状态树,应用程序的所有状态都会被集中到一个比较大的对象中。当应用程序变得非常复杂时,存储对象会变得非常臃肿。为了解决以上问题,Vuex允许我们将store拆分成模块。每个模块都有自己的state、mutation、action、getter,甚至嵌套的子模块——从上到下以相同的方式进行分割。ModuleCollection类ModuleCollection类的构造函数如下,rawModule,runtime=true){if(process.env.NODE_ENV!=='production'){assertRawModule(path,rawModule)}constnewModule=newModule(rawModule,runtime)if(path.length===0){this.root=newModule}else{//子模块注册时会到这里constparent=this.get(path.slice(0,-1))parent.addChild(path[path.length-1],newModule)}//注册嵌套模块if(rawModule.modules){forEachValue(rawModule.modules,(rawChildModule,key)=>{this.register(path.concat(key),rawChildModule,runtime)})}}...}Inregister方法,path参数是实现命名空间的功能,保存模块信息的结构体。撇开新模块的逻辑不谈。if(path.length===0)因为构造函数传入的路径是[],所以判断条件为真,标识了根模块。然后递归判断if(rawModule.modules)如果有modules属性,则重复调用register方法,将当前模块名添加到path参数中。如果我们传入的选项像{modules:{a:{...}}}递归时,路径会变成['a'],为什么要记录路径呢?默认情况下,模块内的动作、突变和获取器在全局命名空间中注册——这使多个模块能够响应相同的突变或动作。如果你想让你的模块有更高的封装性和可复用性,你可以添加namespaced:true让它成为一个带有命名空间的模块。当一个模块被注册时,它所有的getters、actions和mutations都会根据模块注册的路径自动命名。一旦一个模块使用了命名空间功能,我们就必须知道该模块的注册路径,这样当用户写this.$store.a.b.c.state时,才能找到对应模块的属性。Module类Module类的构造函数非常简单。它简单地存储传入的新Store的选项,并提供子模块管理、重写动作和突变方法功能。exportdefaultclassModule{constructor(rawModule,runtime){this.runtime=runtime//存储一些子项this._children=Object.create(null)//存储程序员传递的原始模块对象this._rawModule=rawModuleconstrawState=rawModule.state//存储原始模块的状态this.state=(typeofrawState==='function'?rawState():rawState)||{}}...}ok属性初始化完成。然后继续看Store的构造方法。//将提交和调度绑定到自身conststore=thisconst{dispatch,commit}=thisthis.dispatch=functionboundDispatch(type,payload){returndispatch.call(store,type,payload)}this.commit=functionboundCommit(type,payload,options){returncommit.call(store,type,payload,options)}重载dispatch方法和commit方法,使他们的this强绑定,避免this丢失的情况。如果绑定不显示,在这种情况下,this的指向会变成a=store.commita('xx')//错误,this指向全局。查看核心代码installModule函数//初始化根模块。//这也递归地注册所有子模块//并收集所有模块gettersinsidethis._wrappedGettersinstallModule(this,state,[],this._modules.root)installModule//在命名空间映射中注册if(module.namespaced){if(store._modulesNamespaceMap[namespace]&&process.env.NODE_ENV!=='production'){重复模块名称错误}store._modulesNamespaceMap[namespace]=module}...constlocal=module.context=makeLocalContext(store,namespace,path)module.forEachMutation((mutation,key)=>{constnamespacedType=namespace+keyregisterMutation(store,namespacedType,mutation,local)})...makeLocalContext函数主要是访问commit,dispatch的区别而store在调用的时候会自动添加命名空间路径。代码如下?functionmakeLocalContext(store,namespace,path){constnoNamespace=namespace===''constlocal={dispatch:noNamespace?store.dispatch:(_type,_payload,_options)=>{constargs=unifyObjectStyle(_type,_payload,_options)const{payload,options}=argslet{type}=argsif(!options||!options.root)复制代码{type=namespace+typeif(process.env.NODE_ENV!=='production'&&!store._actions[type]){未定义时间报错}}returnstore.dispatch(type,payload)},commit:...}//getters和state对象必须延迟获取//因为它们会被vmupdateObject.defineProperties(local,{getters:{get:noNamespace?()=>store.getters:()=>makeLocalGetters(store,namespace)},state:{get:()=>getNestedState(store.state,path)}})returnlocal}接下则是把确定义的mutation、action、getter挂载到st矿石实例对象函数registerMutation(store,type,handler,local){constentry=store._mutations[type]||(store._mutations[type]=[])entry.push(functionwrappedMutationHandler(payload){handler.call(store,local.state,payload)})}我们可以看到storagemutation的数据类型是数组,也就是说可以定义多个同名的mutation,Vuex会记录下来。commit方法看。resetStoreVM上面的store对象基本处理完毕。最后一部分就是把state变成一个响应式对象,这样当我们改变state的时候,就会触发相应的sideeffects,比如getters的值的更新,比如触发watch的回调。核心代码如下:store._vm=newVue({data:{$$state:state},computed})Vuex直接使用Vue实例绑定。计算对象由我们定义的getter包装。以上就是newStore(option)中执行的所有代码。最后,让我们看看我们使用最多的提交方法。?commitcommit(_type,_payload,_options){...constmutation={type,payload}constentry=this._mutations[type]this._withCommit(()=>{entry.forEach(functioncommitIterator(handler){handler(payload)})})this._subscribers.slice()//如果订阅者同步调用unsubscribe则浅拷贝以防止迭代器失效.forEach(sub=>sub(mutation,this.state))}gettype然后在_withCommit匹配的突变在内部执行。这也证实了我们上面的猜想,可以注册多个同名突变。_withCommit方法很简单。?_withCommit(fn){constcommitting=this._committingthis._committing=truefn()this._committing=committing}保存提交前的状态,然后将_committing设置为true。执行commit的fn后,恢复。在严格模式下,state的写操作会决定_committing的状态,保证只有commit才能修改state。最后,this._subscribers被调用。看了文档,发现对应的函数如下。subscribe(handler:Function):函数订阅商店的变化。handler会在每次变异完成后被调用,接收变异和变异后的状态作为参数。另外这段代码有点不一样,this._subscribers.slice().forEach,所有的订阅函数都进行了浅拷贝。为了防止在订阅回调中同步退订,修改this._subscribers的长度,导致forEach次数更少,issue。在执行一些回调时,必须考虑回调可能带来的副作用。?#完由于水平有限,以上解读不保证100%?,尽量按源码观看,如有错误请指正。浏览了整篇文章,代码太多了,是不是以后要写点小点的?阅读源码的初衷是学习作者的代码设计和理解,比如Vuex的命名空间、模块初始化、分段管理操作、类设计抽象等。
