想了解Vuex吗?一定要先给这个备注打码!
时间:2023-03-15 23:36:40
科技观察
本文转载自微信公众号《新钛云服务》,作者林宏辉。转载本文请联系新钛云服务公众号。1.Vuex是做什么用的?Vuex用于复杂应用的状态管理(官方的说法是它是一种状态管理模式)。“杀鸡不用屠刀”。对于简单的项目,用不着Vuex这把“杀人刀”。你用什么做简单的项目?使用Vue.js官方提供的“事件总线”即可。二、我们导入的Vuex对象包含什么?我们在使用Vuex的时候怎么使用呢?通常是这样的:importVuefrom'vue';importVuexfrom'vuex';Vue.use(Vuex);newVuex.Store({state:{//放置状态值count:0,str:"abcd234"},getters:{//放置getters方法strLen:state=>state.str.length},//mutations只能是同步操作mutations:{//放置mutationsmethodincrement(state,payload){//在这里改变state中的数据state.count=payload.number;}},//actions可以是异步操作actions:{//placeactionsmethodactionName({commit}){//dosomethingcommit('mutationName')},getSong({commit},id){api.getMusicUrlResource(id).then(res=>{leturl=res.data.data[0].url;}).catch((error)=>{//错误处理console.log(error);});}}});newVue({el:'#app',store,...});importhere传入的Vuex是什么?我们使用console.log来输出它:console.log(Vuex)通过输出,我们发现它的结构如下:可见,导入的Vuex其实是一个对象,里面包含了Store这个构造函数有几个辅助方法比如mapActions、mapGetters、mapMutations和mapState(稍后讨论)。此外,还有一个安装方法。我们发现导入后应该对其执行Vue.use(Vuex)。根据这两条线索,我们了解到Vuex本质上是一个Vue.js插件。3、如何在各个组件中引用创建的store实例?Vuex提供了一种机制,通过store选项将状态从根组件“注入”到每个子组件中(需要调用Vue.use(Vuex)):constapp=newVue({el:'#app',//提供store对象到“商店”选项,它可以将商店的实例注入所有子组件商店,组件:{Counter},模板:`
`})通过在根实例中注册store选项,store实例会被注入到根组件下的所有子组件中,子组件可以通过这个。$store访问权限。四、Vuex中的几个核心概念1.State很好理解,就是状态数据。Vuex管理的是状态,其他的比如Actions和Mutations是用来辅助管理状态的。如果Vue组件发生变化,它们也直接由State驱动。state可以直接通过this.$store.state获取,也可以使用vuex提供的mapState辅助函数将state映射到computed属性computed。2.GettersGetters本质上是用来处理状态的。Getters和State之间的关系就像Vue.js中computed和data之间的关系。getter的返回值会根据其依赖关系缓存起来,只有当其依赖关系的值发生变化时才会重新计算。可以通过this.$store.getters.valueName访问派生状态。或者直接使用辅助函数mapGetters将其映射到本地计算属性。3.Mutations改变Vuexstore状态的唯一方法是提交一个mutation。Vuex中的突变与事件非常相似:每个突变都有一个字符串事件类型(type)和一个回调函数(handler)。这个回调函数是我们实际进行状态更改的地方。您不能直接调用突变处理程序。这个选项更像是事件注册:“当一个增量类型的突变被触发时,调用这个函数。”要唤醒一个mutationhandler,你需要调用相应类型的store.commit方法,它会接受状态作为第一个参数,你也可以传递额外的参数给store.commit,即payloadmutation:conststore=newVuex.Store({state:{count:1},mutations:{increment(state){//改变状态state.count++}}})store.commit('increment')//...mutations:{increment(state,n){state.count+=n}}store.commit('increment',10)在大多数情况下,payload应该是一个对象,它可以包含多个字段,记录的突变将是更具可读性://...mutations:{increment(state,payload){state.count+=payload.amount}}store.commit('increment',{amount:10})//***或者在对象中提交style***mutations:{increment(state,payload){state.count+=payload.amount}}store.commit({type:'increment',amount:10})Mutation需要遵循Vue的响应规则。由于Vuex的store中的状态是响应式的,当我们改变状态时,监控状态的Vue组件也会自动更新。这也意味着Vuex中的mutations也需要遵循与使用Vue相同的注意事项:最好提前在你的store中初始化所有需要的属性。当你需要在对象上添加新属性时,你应该:使用Vue.set(obj,'newProp',123),或者用新对象替换旧对象。例如,使用对象扩展运算符,我们可以这样写:state.obj={...state.obj,newProp:123}Mutationsmustbesynchronousfunctions一个重要的原则是记住mutations必须是同步函数。为什么?请参考以下示例:mutations:{someMutation(state){api.callAsyncMethod(()=>{state.count++})}}现在假设我们正在调试一个应用程序,并在devtool中观察mutation日志。每个突变都会被记录下来,devtools需要捕捉前一个状态和下一个状态的快照。然而,上面例子中的mutation中的异步函数中的回调使得这成为不可能:因为当mutation触发时回调函数还没有被调用,devtools不知道回调函数什么时候被调用——本质上任何状态改变回调函数是不可追踪的。除了this.$store.commit('xxx')这种提交mutation的方式,还有一种在组件中提交mutation的方式,就是使用mapMutations辅助函数将组件中的方法映射到this.$store。犯罪。例如:import{mapMutations}from'vuex'exportdefault{//...methods:{...mapMutations(['increment',//将`this.increment()`映射到`this.$store.commit('increment')`//`mapMutations`也支持payload:'incrementBy'//Map`this.incrementBy(amount)`到`this.$store.commit('incrementBy',amount)`]),...mapMutations({add:'increment'//Map`this.add()`to`this.$store.commit('increment')`})}}经过这样的映射,就可以调用方法way来触发它相应的(映射的)突变提交。比如在上面的例子中调用add()方法就相当于执行了this.$store.commit('increment')。4.ActionsAction类似于mutation,不同的是:Action提交mutation,而不是直接改变state。Action可以包含任何异步操作。Action函数接受一个与store实例具有相同方法和属性的context对象,因此您可以调用context.commit提交一个mutation,或者通过context.state和context.getters获取状态和getters。DistributeActionAction由store.dispatch方法触发:store.dispatch('increment')Actions支持相同的加载方式和对象方式进行分发://distributestore.dispatch('incrementAsync',{amount:10})/inload/Dispatch的形式store.dispatch({type:'incrementAsync',amount:10})另外,你要知道this.$store.dispatch可以处理触发动作的handler函数返回的Promise,this.$store.dispatch仍然返回一个Promise。动作:{actionA({commit}){returnnewPromise((resolve,reject)=>{setTimeout(()=>{commit('someMutation')resolve()},1000)})}}store.dispatch('actionA').then(()=>{//...})5.ModuleModule是什么概念?它实际上是商店的剪裁。由于Vuex使用单一的状态树,整个应用的所有状态都会集中在一个比较大的对象上。然后,当应用程序变得非常复杂时,存储对象很可能变得相当臃肿!Vuex允许我们将存储分成单独的模块(module)。每个模块都有自己的状态、突变、动作、吸气剂,甚至嵌套的子模块——从上到下以相同的方式拆分。(1)模块的本地状态。对于每个模块内部的mutation和getter,接收到的第一个参数是模块的本地状态对象。对于模块内部的getter,根节点状态会作为第三个参数暴露出来。同样,对于模块内部的action,本地状态通过context.state暴露出来,根节点状态为context.rootState:constmoduleA={state:()=>({count:0}),mutations:{increment(state){//这里的`state`对象是模块的局部状态state.count++}},getters:{doubleCount(state){returnstate.count*2},sumWithRootCount(state,getters,rootState){returnstate.count+根状态。计数}},操作:{incrementIfOddOnRootSum({state,commit,rootState}){if((state.count+rootState.count)%2===1){commit('increment')}}}}(2)命名空间默认情况下,模块内的操作、突变和getter都在全局命名空间中注册——这允许多个模块响应相同的突变或操作。如果你想让你的模块有更高的封装性和可复用性,你可以添加namespaced:true让它成为一个带有命名空间的模块。当一个模块被注册时,它所有的getters、actions和mutations都会根据模块注册的路径自动命名。例如:conststore=newVuex.Store({modules:{account:{namespaced:true,//模块内容(moduleassets)state:{...},//模块中的状态已经嵌套,使用`namespaced`属性不会影响它getters:{isAdmin(){...}//->getters['account/isAdmin']},actions:{login(){...}//->dispatch('account/login')},mutations:{login(){...}//->commit('account/login')},//嵌套模块modules:{//继承父模块的命名空间myPage:{state:{...},getters:{profile(){...}//->getters['account/profile']}},//进一步嵌套命名空间posts:{namespaced:true,state:{...},getters:{popular(){...}//->getters['account/posts/popular']}}}}}})启用命名空间的getters和动作将接收到本地化的getters,分发和提交.换句话说,在使用模块资产时,您不需要在同一模块内添加额外的空间名称前缀。更改命名空间属性后,无需修改模块内部的代码。(3)访问具有命名空间的模块中的全局内容(GlobalAssets)。如果要使用全局state和getters,rootState和rootGetters会作为第三和第四个参数传入getter,同时也会通过context对象action的属性传入。如果您需要在全局命名空间中分发操作或提交突变,请将{root:true}作为第三个参数传递给分派或提交。modules:{foo:{namespaced:true,getters:{//在这个模块的getters中,`getters`是本地化的//可以使用getter的第四个参数调用`rootGetters`someGetter(state,getters,rootState,rootGetters){getters.someOtherGetter//->'foo/someOtherGetter'rootGetters.someOtherGetter//->'someOtherGetter'},someOtherGetter:state=>{...}},actions:{//在这个模块中dispatch和commit也是本地化的//它们可以接受`root`属性来访问rootdispatch或commitsomeAction({dispatch,commit,getters,rootGetters}){getters.someGetter//->'foo/someGetter'rootGetters.someGetter//->'someGetter'dispatch('someOtherAction')//->'foo/someOtherAction'dispatch('someOtherAction',null,{root:true})//->'someOtherAction'commit('someMutation')//->'foo/someMutation'commit('someMutation',null,{root:true})//->'someMutation'},someOtherAction(ctx,payload){...}}}}(4)注册全局动作在具有命名空间的模块中如果您需要在具有命名空间的模块中注册全局操作,您可以加上root:true,把这个action的定义放在functionhandler中。例如:{actions:{someOtherAction({dispatch}){dispatch('someAction')}},modules:{foo:{namespaced:true,actions:{someAction:{root:true,handler(namespacedContext,payload){.。...mapState({a:state=>state.some.nested.module.a,b:state=>state.some.nested.module.b})},方法:{...mapActions(['some/nested/module/foo',//->this['some/nested/module/foo']()'some/nested/module/bar'//->this['some/nested/module/bar']()])}对于这种情况,您可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。所以上面的例子可以简化为:computed:{...mapState('some/nested/module',{a:state=>state.a,b:state=>state.b})},methods:{...mapActions('some/nested/module',['foo',//->this.foo()'bar'//->this.bar()])}(6)模块动态注册并在store创建之后,就可以使用store.registerModule方法注册模块了:importVuexfrom'vuex'conststore=newVuex.Store({/*options*/})//注册模块`myModule`store.registerModule('myModule',{//...})//注册嵌套模块`nested/myModule`store.registerModule(['nested','myModule'],{//...})然后就可以通过store.state.myModule和store.state。nested.myModule访问模块的状态。模块动态注册功能允许其他Vue插件使用Vuex通过将新模块附加到商店来管理状态。例如vuex-router-sync插件通过动态注册模块将vue-router和vuex结合起来,实现应用的路由状态管理。您还可以使用store.unregisterModule(moduleName)动态卸载模块。请注意,您不能使用此方法卸载静态模块(即在创建商店时声明的模块)。请注意,您可以使用store.hasModule(moduleName)方法检查模块是否已注册到商店。保存状态在注册一个新模块时,您很可能希望保存过去的状态,例如来自服务器呈现的应用程序。您可以通过preserveState选项将其存档:store.registerModule('a',module,{preserveState:true})。当你设置preserveState:true时,模块被注册并且动作、突变和getters被添加到商店,但状态不是。这假设商店的状态已经包含模块的状态并且您不想覆盖它。(7)模块复用有时我们可能需要创建一个模块的多个实例,例如:创建多个商店,它们共享同一个模块(例如当runInNewContext选项为false或'once'时,为了避免出现Singletonofstate(opensnewwindow))在一个store中多次注册同一个模块如果我们使用一个纯对象来声明模块的状态,那么这个状态对象将通过引用共享,导致状态对象在调用时被修改store或模块之间数据相互污染的问题。实际上,这与Vue组件中的数据存在同样的问题。所以解决方案是相同的——使用一个函数来声明模块状态(仅2.3.0+支持):constMyReusableModule={state:()=>({foo:'bar'}),//mutation,actionandgetterWait...}5.Vuex中的表单处理在严格模式下使用Vuex时,在属于Vuex的状态上使用v-model是很棘手的:
这里假设obj是一个属于计算属性中返回的Vuex存储的对象。当用户进入时,v-model会尝试直接修改obj.message。在严格模式下,由于变异函数中没有进行这种修改,所以这里会报错。用“Vuex思维”解决这个问题的方法是:给绑定value,然后监听input或者change事件,在事件回调中调用一个方法:computed:{...mapState({message:state=>state.obj.message})},methods:{updateMessage(e){this.$store.commit('updateMessage',e.target.value)}}这是变异函数://...mutations:{updateMessage(state,message){state.obj.message=message}}不可否认,这样做比简单地使用“v-model+partialstate”要快得多"更冗长,也失去了v-model的一些有用特性。另一种方法是使用带有设置器的双向绑定计算属性:computed:{message:{get(){returnthis.$store.state.obj.message},set(value){this.$store.commit('updateMessage',value)}}}