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

vuex源码分析(一)

时间:2023-04-05 21:04:29 HTML5

vuex源码分析系列大概会分三篇博客,为什么分三篇博客呢,因为博客太多要写了。你苦苦看着,我也懒得写了。这是分析vuex源码的vuex目录图。我们先来分析一下vuex的源码。入口文件在src/store.js文件中。exportdefault{//主要代码,状态存储类Store,//插件安装install,//versionversion:'__VERSION__',mapState,mapMutations,mapGetters,mapActions,createNamespacedHelpers}一个一个,Store是状态存储类由vuex提供。通常我们使用Vuex是通过创建Store例如install方法配合Vue.use方法使用的。install方法是编写Vue插件的通用公式。我们先看一下代码。exportfunctioninstall(_Vue){if(Vue&&_Vue===Vue){if(process.env.NODE_ENV!=='production'){console.error('[vuex]已经安装。Vue.use(Vuex)应该只调用一次。')}return}Vue=_VueapplyMixin(Vue)}This方法的作用是什么:当窗口上有Vue对象时,会手动编写install方法传入使用通过Vue。install方法中有一个函数applyMixin,来自于cloudsrc/mixin.js,下面是mixin.js的代码version.split('.')[0])//如果vue的版本大于2,则vuex初始化beforeCreateif(version>=2){Vue.mixin({beforeCreate:vuexInit})}else{//覆盖init并注入vuexinit过程//以实现1.x向后兼容性。//与vue1兼容的版本const_init=Vue.prototype._initVue.prototype._init=function(options={}){options.init=options.init?[vuexInit].concat(options.init):vuexInit_init.call(this,options)}}/***Vuex初始化钩子,注入到每个实例的初始化钩子列表中。*///为vue实例注册一个$store属性,类似于我们使用vue.$routefunctionvuexInit(){constoptions=this.$options//store注入if(options.store){this.$store=typeofoptions.store==='函数'?options.store():options.store}elseif(options.parent&&opactions.parent.$store){this.$store=options.parent.$store}}}这段代码的总体意思是在vue语句循环中初始化vuex,同时为了对vue的各个版本进行兼容,vuexInit就是vuex的初始化。为什么我们可以使用vue.$store方法,因为在vuexInit.vue实例中添加了$store属性。Store构造函数首先查看构造函数constructorconstructor(options={}){//如果尚未完成且`window`具有`Vue`,则自动安装。//为了让用户在某些情况下避免自动安装,//此代码应放在此处。参见#731//判断window.vue是否存在,如果不存在则安装if(!Vue&&typeofwindow!=='undefined'&&window.Vue){install(window.Vue)}//这个在一些链接中判断在开发过程中,vuex需要创建vuex//store实例之前必须使用这个方法Vue.use(Vuex),判断是否可以使用promiseif(process.env.NODE_ENV!=='production'){assert(Vue,`必须在创建商店实例之前调用Vue.use(Vuex)。`)assert(typeofPromise!=='undefined',`vuex需要在此浏览器中使用Promisepolyfill。`)assert(thisinstanceofStore,`Store必须使用new运算符调用。`)}//提取参数const{plugins=[],strict=false}=optionslet{state={}}=optionsif(typeofstate==='函数'){state=state()||{}}//storeinternalstate//初始化store的内部状态,Object.create(null)是ES5的一个方法,Object.create(//object)object就是你要继承的对象,如果是null,然后创建一个纯对象_modules=newModuleCollection(options)this._modulesNamespaceMap=Object.create(null)this._subscribers=[]this._watcherVM=newVue()//将提交和分派绑定到自身conststore=thisconst{dispatch,commit}=this//定义调度方法this.dispatch=functionboundDispatch(type,payload){returndispatch.call(store,type,payload)}//定义提交方法this.commit=functionboundCommit(type,payload,options){returncommit.call(store,type,payload,options)}//严格模式//严格模式this.strict=strict//初始化根模块。//这也递归注册了所有子模块//并收集了this._wrappedGetters中的所有模块getters//初始化根模块,递归注册所有子模块,收集gettersinstallModule(this,state,[],this._modules.root)//初始化负责反应性的storevm//(还将_wrappedGetters注册为计算属性)//重置vm状态,并将getter转换为计算属性resetStoreVM(this,state)//应用插件//执行每个插件里面的函数plugins.forEach(plugin=>plugin(this))if(Vue.config.devtools){devtoolPlugin(this)}}因为vuex是es6写的,我真的觉得es6模块简直就是一个法宝。我曾经很好奇他们是怎么写出像Echarts这样的80,000行的库的。上次看到一个百度求职者问Echarts的源码不可怕!后来才知道模块化。现在es6引入了import这种模块化的语句,方便我们编写庞大的代码。if(!Vue&&typeofwindow!=='undefined'&&window.Vue){install(window.Vue)}这段代码检查window.Vue是否存在,不存在则调用install方法assert(Vue,`必须在创建商店实例之前调用Vue.use(Vuex)。`)这行代码用于确保在我们实例化商店之前vue已经存在。assert(typeofPromise!=='undefined',`vuexrequiresaPromisepolyfillinthisbrowser.`)这行代码用来检测是否支持Promise,因为vuex中使用的是Promise,Promise是es6的语法,但是有些浏览器浏览器不支持es6,所以我们需要在package.json中添加babel-polyfill来支持es6。我们看下一段代码const{plugins=[],strict=false}=options这里使用了es6的解构赋值来获取options中的plugins。es6的解构赋值在我的《前端面试之ES6》中有提到,大致[a,b,c]=[1,2,3]等价于a=1,b=2,c=3。后一句的代码类似于获取state的值。this._committing=falsethis._actions=Object.create(null)this._actionSubscribers=[]this._mutations=Object.create(null)this._wrappedGetters=Object.create(null)this._modules=newModuleCollection(选项)this._modulesNamespaceMap=Object.create(null)this._subscribers=[]this._watcherVM=newVue()这个主要用来创建一些内部属性,为什么要加_这个用来标识属性,_表示内部属性,我们在外部调用这些属性时需要小心。this._committing标记一个提交状态this._actions用于存储所有用户定义的动作this._actionSubscribersthis._mutations用于存储所有用户定义的突变this._wrappedGetters用于存储所有用户定义的gettersthis._modulesthis._subscribers用于存储所有对突变变化的订阅者this._watcherVM是一个Vue对象的实例,主要是利用Vue实例方法$watch来观察变化的commit和dispatch方法。之后,installModule(this,state,[],options)会把我们options传入的各种属性模块注册并安装。resetStoreVM(this,state)resetStoreVM方法是初始化store._vm,观察state和getters的变化;最后,应用传入的插件。下面是installModule函数的实现installModule(store,rootState,path,module,hot){constisRoot=!path.lengthconstnamespace=store._modules.getNamespace(path)//注册到namespacemapif(module.namespaced){store._modulesNamespaceMap[namespace]=module}//设置状态if(!isRoot&&!hot){constparentState=getNestedState(rootState,path.slice(0,-1))constmoduleName=path[path.length-1]store._withCommit(()=>{Vue.set(parentState,moduleName,module.state)})}constlocal=module.context=makeLocalContext(store,namespace,path)module.forEachMutation((mutation,key)=>{constnamespacedType=namespace+keyregisterMutation(store,namespacedType,mutation,local)})模块。forEachAction((action,key)=>{consttype=action.root?key:namespace+keyconsthandler=action.handler||actionregisterAction(store,type,handler,local)})module.forEachGetter((getter,key)=>{constnamespacedType=namespace+keyregisterGetter(store,namespacedType,getter,local)})module.forEachChild((child,key)=>{installModule(store,rootState,path.concat(key),child,hot)})}这个函数的主要作用是什么:初始化组件树的根组件,注册所有的子组件,将所有的getter存放在this._wrapperdGetters属性中。这个函数接受5个函数,store,rootState,path,module,hot我们来看看它们分别代表什么:store:表示当前Store实例rootState:表示根状态,path:其他参数的含义比较容易理解,那么这条路怎么理解。我们可以将商店实例视为模块的集合。每个集合也是商店的一个实例。那么path可以想象成一个层级关系。当你有rootState和path时,你可以在Path中找到本地State。然后每次getters或setterschangeislocalStatemodule:表示当前安装的模块hot:当模块动态变化或热更新时为trueconstisRoot=!path.lengthconstnamespace=store._modules.getNamespace(path)用于传递长度Path的of用于确定它是否是根。下面这句是注册命名空间module.forEachMutation((mutation,key)=>{constnamespacedType=namespace+keyregisterMutation(store,namespacedType,mutation,local)})就是注册mutation到module中。后面几句效果一样,就不一一解释了。module.forEachChild((child,key)=>{installModule(store,rootState,path.concat(key),child,hot)})这里是通过遍历Modules递归调用installModule安装模块。if(!isRoot&&!hot){constparentState=getNestedState(rootState,path.slice(0,-1))constmoduleName=path[path.length-1]store._withCommit(()=>{Vue.set(parentState,moduleName,module.state)})}这一段是用来判断如果不是root且Hot为false时要执行的俄语操作,这里是一个函数getNestedState(state,path),函数内容如下:functiongetNestedState(state,path){returnpath.length?path.reduce((state,key)=>state[key],state):state}这个函数的意思是获取module的parent的state,获取moduleName,然后调用store._withCommit()方法store._withCommit(()=>{Vue.set(parentState,moduleName,module.state)})将当前模块的状态添加到parentState中,我们使用_withCommit()方法,我们将_withCommit的方法留给commit方法。