当前位置: 首页 > Web前端 > HTML5

Vuex源码分析(二)

时间:2023-04-05 17:00:05 HTML5

继上面的constructor函数,我们继续说下面的内容getstate(){returnthis._vm._data.$$state}setstate(v){if(process.env.NODE_ENV!=='production'){assert(false,`Usestore.replaceState()toexplicitreplacestorestate.`)}}get和set方法在es6中都有语法,get语法是绑定对象属性的查询属性时调用的函数,set语法是查询对象的属性时调用的函数。当状态属性改变时,状态的值将被返回。set是用来在我们设置状态的时候检测是否是生产环境。如果不是生产环境,则会执行断点。commit(_type,_payload,_options){//检查对象样式提交const{type,payload,options}=unifyObjectStyle(_type,_payload,_options)constmutation={type,payload}constentry=this._mutations[type]if(!entry){if(process.env.NODE_ENV!=='production'){console.error(`[vuex]unknownmutationtype:${type}`)}return}this._withCommit(()=>{entry.forEach(functioncommitIterator(handler){handler(payload)})})this._subscribers.forEach(sub=>sub(mutation,this.state))if(process.env.NODE_ENV!=='production'&&options&&options.silent){console.warn(`[vuex]mutationtype:${type}.Silentoptionhasbeenremoved.`+'Usethefilterfunctionalityinthevue-devtools')}}commit函数接受三个参数,_type表示突变的类型,_payload表示外部的参数,options表示一些配置。函数中有一个叫unfyObjectStyle()的函数,看看他的代码type}if(process.env.NODE_ENV!=='production'){assert(typeoftype==='string',`预期字符串为类型,但发现${typeoftype}。`)}return{type,payload,options}}这个函数很简单,就是获取类型为对象时单独传入的参数。继续看commit函数,根据unifyObjectStyle()处理的类型找到mutations。withCommiting的代码如下_withCommit(fn){constcommitting=this._committingthis._committing=truefn()this._committing=committing}这个函数是最重要的部分,就是为什么设置this._committing=true会这样.是因为我们在观察state状态的时候,可以通过看this._commiting的值来判断state修改是否错误。如果是false,是不是状态修改有问题?commit之后的部分比较好理解,使用this._withCommit方法提交mutation。this._subscribers的内容是什么?正如我们在上一篇文章中提到的,它用于存储所有订阅者对突变的更改。遍历this._subscribers进行回调。options&&options.silent在这里,我不太明白黄毅老师说的是静默模式:静默模式是在用户不干涉的情况下,电脑自行处理的情况。以下是调度代码dispatch(_type,_payload){//检查对象样式dispatchconst{type,payload}=unifyObjectStyle(_type,_payload)constaction={type,payload}constentry=this._actions[type]if(!entry){if(process.env.NODE_ENV!=='production'){console.error(`[vuex]unknownactiontype:${type}`)}return}this._actionSubscribers.forEach(sub=>sub(action,this.state))returnentry.length>1?Promise.all(entry.map(handler=>handler(payload))):entry[0](payload)}前几行代码跟commit函数很像,获取值,然后找到对应的action。this._actionSubscribers和之前的commit方法的区别在于最后使用了这段代码:returnentry.length>1?Promise.all(entry.map(handler=>handler(payload))):entry[0](payload)如果获取到的action个数大于一个,则使用Promise.all()遍历每个action并调用返回回调函数。前几天看到一道面试题。如果一个进程需要8秒,另一个进程需要3秒,怎么办?为了让两个过程耗时最短,解决方案是Promise.all([a,b])注意里面必须是一个数组,这样两个过程才能同时进行。如果长度为1,调用entry0subscribe(fn){returngenericSubscribe(fn,this._subscribers)}subscribeAction(fn){returngenericSubscribe(fn,this._actionSubscribers)}functiongenericSubscribe(fn,subs){if(subs.indexOf(fn)<0){subs.push(fn)}return()=>{consti=subs.indexOf(fn)if(i>-1){subs.splice(i,1)}}}查看genericSubscribe做了什么。它接受两个参数,即调整subs。如果subs中没有fn,则push(fn)给subs。返回移除了fn的subs。那么上面的subscribe和subscribeAction就是处理传入的fn然后传给genericSubscribe()函数watch(getter,cb,options){if(process.env.NODE_ENV!=='production'){assert(typeofgetter==='function',`store.watch只接受一个函数。`)}returnthis._watcherVM.$watch(()=>getter(this.state,this.getters),cb,options)}watchisresponsive检测getter方法的返回值,当值改变时,回调发生。该函数首先断言watch的getter必须是方法,然后使用vue实例对象this._watcherVM的watch方法replaceState(state){this._withCommit(()=>{this._vm._data.$$state=state})}用于修改state,调用this._withCommit方法修改StoreregisterModule的rootStateregisterModule(path,rawModule,options={}){if(typeofpath==='string')path=[path]if(process.env.NODE_ENV!=='production'){assert(Array.isArray(path),`模块路径必须是字符串或数组。`)assert(path.length>0,'无法使用registerModule注册根模块。')}this._modules.register(path,rawModule)installModule(this,this.state,path,this._modules.get(path),options.preserveState)//重置存储toupdategetters...resetStoreVM(this,this.state)}字面意思可以看出动态注册Module的意思,dynamic是什么意思。有一些异步操作,我们需要使用这个函数来动态注册模块。这个孩子有一个函数resetStoreVM,我们来看看他的现方法函数resetStoreVM(store,state,hot){constoldVm=store._vm//bindstorepublicgettersstore.getters={}constwrappedGetters=store._wrappedGettersconstcomputed={}forEachValue(wrappedGetters,(fn,key)=>{//使用computed来利用其惰性缓存机制computed[key]=()=>fn(store)Object.defineProperty(store.getters,key,{get:()=>store._vm[key],enumerable:true//对于本地getter})})//使用Vue实例来存储状态树//抑制警告,以防用户添加//一些funkyglobalmixinsconstsilent=Vue.config.silentVue.config.silent=truestore._vm=newVue({data:{$$state:state},computed})Vue.config.silent=silent//enablestrict新vm的模式if(store.strict){enableStrictMode(store)}if(oldVm){if(hot){//在所有订阅的观察者中调度更改//强制getter重新评估以进行热重载。store._withCommit(()=>{oldVm._data.$$state=null})}Vue.nextTick(()=>oldVm.$destroy())}}这个函数的意思是监听数据。首先,初始化store.gettersforEachValue以循环遍历所有处理过的getter,并通过Object.defineProperty创建一个新的计算对象用于存储。然后定义store.getter的属性key,设置enumerable为true,表示可以枚举。在存储之前的Vue.config.slient之前,将Vue.config.slient设置为true,取消所有的警告和提醒。store._vm=newVue({data:{$$state:state},computed})这很简单。创建一个Vue的实例,然后把state放在data里,用getters把value填在computed里。if(store.strict){enableStrictMode(store)}如果是严格模式,则调用enableStrictMode()下面是enableStrictMode的代码functionenableStrictMode(store){store._vm.$watch(function(){returnthis._data.$$state},()=>{if(process.env.NODE_ENV!=='production'){assert(store._committing,`不要在突变处理程序之外改变vuex存储状态。`)}},{deep:true,sync:true})}这个函数是做什么的?assert中写的意识是,如果设置了严格模式,则不允许用户修改stateif(oldVm){if(hot){//dispatchchangesinallsubscribedwatchers//强制getter重新求值进行热重载。store._withCommit(()=>{oldVm._data.$$state=null})}Vue.nextTick(()=>oldVm.$destroy())}如果oldVm存在且hot为True,则设置state在原来的vm为空,所有原来的getter都将被重新计算。为什么会这样?前面我们创建了一个vue实例,getter是作为computed使用的。我们知道在Vue中,一旦value改变,computed又会改变。unregisterModuleunregisterModule(path){if(typeofpath==='string')path=[path]if(process.env.NODE_ENV!=='production'){assert(Array.isArray(path),`模块路径必须是字符串或数组。`)}this._modules.unregister(path)this._withCommit(()=>{constparentState=getNestedState(this.state,path.slice(0,-1))Vue.delete(parentState,path[path.length-1])})resetStore(this)}跟随数registerModule类似,用于动态加载module。这里说一下this._withCommit的作用,从父状态中删除当前模块的状态对象,最后调用resetStore函数,下面是resetStore函数functionresetStore(store,hot){store._actions=Object.create(null)store._mutations=Object.create(null)store._wrappedGetters=Object.create(null)store._modulesNamespaceMap=Object.create(null)conststate=store.state//初始化所有模块installModule(store,state,[],store._modules.root,true)//resetvmresetStoreVM(store,state,hot)}这个方法的作用是重置store,installModule是重新安装_aciton,_mutations,_wrappedGetters,resetStoreVM的属性然后重置storevm对象的_。hotUpdate(newOptions){this._modules.update(newOptions)resetStore(this,true)}函数是热加载新的动作和突变。先更新模块,再更新商店。vuex中最重要的类Store就讲完了。我们正在谈论其他重要功能。exportfunctiondeepCopy(obj,cache=[]){//如果obj是不可变值则返回if(obj===null||typeofobj!=='object'){returnobj}//如果obj被命中,它是循环结构consthit=find(cache,c=>c.original===obj)if(hit){returnhit.copy}constcopy=Array.isArray(obj)?[]:{}//首先将副本放入缓存//因为我们想在递归deepCopy中引用它cache.push({original:obj,copy})Object.keys(obj).forEach(key=>{copy[key]=deepCopy(obj[key],cache)})returncopy}该函数的原理是构造一个新对象,遍历原对象或数组,递归调用deepCopy。这里加了一个缓存,为什么?这是为了提高性能。例如,在Facinaciate中,我们可以使用这种方法来提高性能,而不必每次都重新计算已经计算过的东西。这次先说到这里,其他重要的事情下次再说。Function下次我决定不再使用这种将所有功能解释清楚的方法。这个工程量太大了,接下来源码分析按照我总结的方式学习源码