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

《Vue源码学习》想知道Vuex的实现原理吗?

时间:2023-03-29 13:00:34 HTML

大家好,我是林三鑫,Vuex是专门为Vue.js应用开发的状态管理模式。它使用集中存储来管理应用程序所有组件的状态,并使用相应的规则来确保状态以可预测的方式变化。我应该什么时候使用Vuex?Vuex可以帮助我们管理共享状态,并且附带了更多的概念和框架。这需要在短期利益和长期利益之间进行权衡。如果您不打算开发大型单页应用程序,使用Vuex可能会很乏味且多余。这是真的——如果你的应用足够简单,你最好不要使用Vuex。您只需要一个简单的商店模式(opensnewwindow)。但是,如果你需要构建一个中大型的单页应用,你很可能会考虑如何在组件外部更好地管理状态,而Vuex将成为一个自然而然的选择。引用Redux的作者DanAbramov的话:Flux架构就像眼镜:你会知道什么时候需要它。查看Vuex的使用InstallYarnyarnaddvuexNPMnpminstallvuex--save在模块化打包系统中,必须通过Vue.use()显式安装Vuex:importVuefrom'vue'importVuexfrom'vuex'Vue.use(Vuex)registerstoreimportVuefrom'vue'importVuexfrom'vuex'Vue.use(Vuex)conststore=newVuex.Store({state:{count:0},mutations:{increment(state){state.count++}}})newVue({el:'#app',store//register})StatecommonuseconstCounter={template:`

{{count}}
`,computed:{count(){returnthis.$store.state.count}}}每当this.$store.state.count改变时,计算属性将被重新计算,相关的DOM将被更新。辅助函数当组件需要获得多个状态时,将这些状态声明为计算属性有些重复和多余。为了解决这个问题,我们可以使用mapState辅助函数来帮助我们生成计算属性,让您按下更少的键://在单独构建的版本中,辅助函数是Vuex.mapStateimport{mapState}from'vuex'exportdefault{//...computed:mapState({//箭头函数可以让代码更简洁:'count',//为了能够使用`this`获取本地状态,您必须使用常规函数countPlusLocalState(state){returnstate.count+this.localCount}})}当nameof映射的计算属性与状态的子节点名称相同,我们也可以将字符串数组传递给mapState。computed:mapState([//mapthis.counttostore.state.count'count'])objectspreadoperatorcomputed:{localComputed(){/*...*/},//使用对象展开运算符到这个对象混入外部对象...mapState({//...})}Getter通常使用接受状态作为第一个参数的Getters:conststore=newVuex.Store({state:{todos:[{id:1,text:'...',done:true},{id:2,text:'...',done:false}]},getters:{doneTodos:state=>{returnstate.todos.filter}(todo=>todo.done)}}})Getter也可以接受其他getter作为第二个参数:getters:{//...doneTodosCount:(state,getters)=>{returngetters.doneTodos.length}}我们可以在任何组件中轻松使用它:computed:{doneTodosCount(){returnthis.$store.getters.doneTodosCount}}请注意,当通过其中一个属性访问时,getter被缓存为Vue反应系统的一部分。(computedcache也是一样,后面会专门写一篇文章讲)你也可以通过让getter返回一个函数的方式来给getter传递参数。当您对商店中的数组进行查询时很有用。getters:{//...getTodoById:(state)=>(id)=>{returnstate.todos.find(todo=>todo.id===id)}}store.getters.getTodoById(2)//->{id:2,text:'...',done:false}辅助函数mapGetters辅助函数只是将store中的getter映射到本地计算属性:import{mapGetters}from'vuex'exportdefault{//...computed:{//使用对象扩展运算符将getter混合到计算对象中...mapGetters(['doneTodosCount','anotherGetter',//...])}}如果你想要一个getter属性取另一个名字并使用对象形式:...mapGetters({//Map`this.doneCount`to`this.$store.getters.doneTodosCount`doneCount:'doneTodosCount'})Muations通常使用mutationsinVuex非常类似于事件:每个mutation都有一个字符串事件类型(type)和一个回调函数(handler)conststore=newVuex.Store({state:{count:1},mutations:{increment(state,n){//n是一个参数,可以设置也可以不设置,这个参数也叫"load"//改变状态state.count++}}})//使用this.$store.commit('increment',10)辅助Functionimport{mapMutations}from'vuex'exportdefault{//...方法:{...mapMutations(['increment',//将`this.increment()`映射到this.$store.commit('increment')`//`mapMutations`也支持负载:'incrementBy'//将`this.incrementBy(amount)`映射到`this.$store。commit('incrementBy',amount)`]),...mapMutations({add:'increment'//将`this.add()`映射到`this.$store.commit('increment')`})}在突变中混合异步调用会使您的程序难以调试例如,当您调用两个包含异步回调的突变来更改状态时,您如何知道何时回调以及先回调哪个?这就是我们区分这两个概念的原因。在Vuex中,突变都是同步事务。ActionAction类似于mutation,不同的是Action提交mutation,而不是直接改变state。动作可以包含任意异步操作。conststore=newVuex.Store({state:{count:0},mutations:{increment(state){state.count++}},actions:{//Action函数接受一个与store具有相同方法和属性的上下文instanceObjectincrementAsync(context,n){//可以传递"payload"nsetTimeout(()=>{context.commit('increment')},1000)}}})//执行//分发存储在表单中的有效载荷。dispatch('incrementAsync',{amount:10})//distributestore.dispatch({type:'incrementAsync',amount:10})helperfunctionimport{mapActions}from'vuex'exportdefault{//...方法:{...mapActions(['increment',//将`this.increment()`映射到`this.$store.dispatch('increment')`//`mapActions`也支持负载:'incrementBy'//将`this.incrementBy(amount)`映射到`this.$store.dispatch('incrementBy',amount)`]),...mapActions({add:'increment'//映射`this.add()`映射到`this.$store.dispatch('increment')`})}}组合操作//假设getData()和getOtherData()返回Promiseactions:{asyncactionA({commit}){commit('gotData',awaitgetData())},asyncacmotionB({dispatch,commit}){awaitdispatch('actionA')//等待actionA完成commit('gotOtherData',awaitgetOtherData())}}Module由于使用了单个状态树,所以应用的所有状态将集中在一个比较大的对象中当应用程序变得非常复杂时,存储对象会变得相当臃肿。为了解决以上问题,Vuex允许我们将store拆分成模块。每个模块都有自己的状态、突变、动作、getter,甚至还有嵌套的子模块——从上到下以相同的方式拆分:constmoduleA={state:()=>({...}),mutations:{...},动作:{...},吸气剂:{...}}constmoduleB={状态:()=>({...}),突变:{...},动作:{...}}conststore=newVuex.Store({modules:{a:moduleA,b:moduleB}})store.state.a//->moduleA的状态store.state.b//->状态moduleB模块内部的mutations和getters,接收到的第一个参数是模块的本地状态对象。constmoduleA={state:()=>({count:0}),mutations:{increment(state){//这里的`state`对象是模块的本地状态state.count++}},getters:{doubleCount(state){//这里的`state`对象是模块的局部状态returnstate.count*2}}}同理,对于模块内部的action,局部状态是通过context.state暴露出来的,根节点stateiscontext.rootState:constmoduleA={//...actions:{incrementIfOddOnRootSum({state,commit,rootState}){if((state.count+rootState.count)%2===1){commit('increment')}}}}对于模块内部的getter,根节点的状态会暴露为第三个参数:constmoduleA={//...getters:{sumWithRootCount(state,getters,rootState){returnstate.count+rootState.count}}}module的namespace部分,后面有机会再说简单的原理。看完Vuex源码文件,发现有很多。我会讲一些我们最常用的函数的源码。其实用过Vuex的同学都知道,我们在页面或者组件中调用this.$store.xxx。其实我们只需要将你创建的store对象赋值给页面或者组件中的$store变量即可。Vuex的原理是:使用全局mixinMixin将你创建的store对象mix到每一个Vue实例中,那么什么是全局mixin呢?例如:importVuefrom'vue'//globalmixintoVue.mixin({created(){console.log('我是林三鑫')}})//后面创建的Vue实例会输出'我是林三鑫'consta=newVue({//这里什么都没有,但是可以输出'我是林三鑫'})//=>"我是林三鑫"constb=newVue({//这里什么都没有,但是可以输出'我是林三鑫'})//=>"我我是林三心”看??懂上面这个例子的就知道了。同理,将代码console.log('我是林三鑫')换成一段可以这样代码:将store赋值给实例的$store属性,实现:代码实现目录vuex.js//vuex.jsletVue;//设置了install方法是因为Vue.use(xxx)会执行xxx的install方法constinstall=(v)=>{//参数v负责接收vue实例Vue=v;//globalmixinVue.mixin({beforeCreate(){if(this.$options&&this.$options.store){//根页面,直接把自己身上的store赋给自己的$store,这也解释了为什么使用vuex时需要先将store放在入口文件main.js中的根Vue实例中this.$store=this.$options.store;}else{//除根页面外,将$赋值storeofthesuperiortoitsown$storethis.$store=this.$parent&&this.$parent.$store;}},})}//createClassStoreclassStore{constructor(options){//选项接收incomingstoreobjectthis.vm=newVue({//确保状态是响应式的data:{state:options.state}});//getter让getters=options.getters||{};this.getters={};console.log(Object.keys(this.getters))Object.keys(getters).forEach(getterName=>{Object.defineProperty(this.getters,getterName,{get:()=>{returngetters[getterName](this.state);}})})//变异letmutations=options.mutations||{};this.mutations={};Object.keys(mutations).forEach(mutationName=>{this.mutations[mutationName]=payload=>{mutations[mutationName](this.state,payload);}})//action让actions=options.actions||{};this.actions={};Object.keys(actions).forEach(actionName=>{this.actions[actionName]=payload=>{actions[actionName](this.state,payload);}})}//获取状态时,直接返回获取状态(){返回this.vm.sta特;}//commit方法,执行mutations的'name'方法commit(name,payload){this.mutations[name](payload);}//dispatch方法,执行actions的'name'方法dispatch(name,payload){this.actions[name](payload);}}//暴露install方法和Store类exportdefault{install,Store}index.js//index.jsimportVuefrom'vue';importvuexfrom'./vuex';//引入vuex.js暴露的对象Vue.use(vuex);//会执行vuex对象中的install方法,即全局mixin//实例一个Store类,并暴露exportdefaultnewvuex.Store({state:{num:1},getters:{getNum(state){returnstate.num*2;}},mutations:{in(state,payload){state.num+=payload;},de(state,payload){state.num-=payload;}},动作:{in(state,payload){setTimeout(()=>{state.num+=payload;},2000)}}})main.js//main.jsimportVuefrom'vue';importAppfrom'./App.vue'从'./store/index'导入商店;//导入index.jsnewVue({store,//把store挂在根实例上el:'#app',components:{App},template:'',})至此,vuex的state、mutations、getters、action都简单实现了。以后有机会专门写一篇Vue实现mudule的文章。不提倡全局mixin,连mixin也不提倡,不要乱用!结语我是林三鑫,一个狂热的前端菜鸟程序员。如果你有上进心,喜欢前端,想学前端,那我们可以交个朋友,一起钓鱼哈哈,摸摸鱼群,加我,请注意[思想]