🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## **1. 为什么要模块化** * 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 * 为了解决以上问题,Vuex 允许我们将 store 分割成**模块(module)**。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割: ## **2. 模块入门** ### **2.1 单文件多module** 用vue create 创建的简单demo 1. `main.js` ~~~ import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; Vue.config.productionTip = false; new Vue({ router, store, render: h => h(App) }).$mount("#app"); ~~~ 2. `store/index.js` ~~~ import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const moduleA = { state: () => ({ conut: 11, message: '我是A模块' }), mutations: {}, actions: {}, getters: {} } const moduleB = { state: () => ({ message: '我来自B模块-_-', }), mutations: {}, actions: {} } export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: { //modules中添加模块 a: moduleA, b: moduleB } }); ~~~ 3. `about.js` ~~~ <template> <div class="about"> <h1>This is an about page</h1> <br> <h2> {{this.$store.state.a.message}} </h2> <br> <h2> {{this.$store.state.b.message}} </h2> </div> </template> ~~~ ![](https://img.kancloud.cn/06/78/06781f19972a71855914ec46fbc70faf_763x406.png) **注意:模块中mutation、getters和actions接收的参数是本地化的state,即本身的数据** ### **2.2 一个module一个文件** 上边的例子显然不符合vuex木块化的初衷,store/index.js文件还是很bloated,所以我们要把模块拆出来,成单个的module文件 例如:将a和b模块拆出来,对应store下边连个目录 ![](https://img.kancloud.cn/48/2f/482f5d1f56a427314d4c54421760be02_272x126.png) 1. `store/moduleA/index.js` ~~~ export const moduleA = { state: () => ({ conut: 11, message: '我是A模块' }), mutations: {}, actions: {}, getters: {} } ~~~ 2. `store/moduleB/index.js` ~~~ export const moduleB = { state: () => ({ message: '我来自B模块-_-', }), mutations: {}, actions: {} } ~~~ 3. store/index.js ~~~ import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); import {moduleA} from './moduleA' import {moduleB} from './moduleB' export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: { a: moduleA, b: moduleB } }); ~~~ **`import {moduleA} from './moduleA'` 默认加载moduleA目录下的index.js文件 如果有其他文件,直接加文件名即可,例如`import {moduleA} from './moduleA/test'` 加载moduleA目录下的test.js文件** ### **2.3 子模块获取根模块的state** 注意:在开启和未开始名称空间后,访问的区别,此时getters,actions和mutions都是注册到全局命名空间中的 1. 对于模块内部的 action,局部状态通过`context.state`暴露出来,根节点状态则为`context.rootState`: 2. 对于模块内部的 `getter`,根节点状态会作为第三个参数暴露出来: #### 2.3.1 getters访问 1. `moduleA/index.js` ~~~ getters: { rootMessages(state, getters, rootState) { console.log("huoquget"); return state.count + rootState.rootMessage } } ~~~ 2. 访问方式 1)使用`this.$store.getters.rootMessages`直接访问 2)使用`mapGetters`辅助函数 ~~~ <script> import {mapGetters} from 'vuex' //引入辅助函数 export default { computed: { ...mapGetters(['rootMessages']) } } </script> ~~~ ` ...mapGetters(['rootMessages']) `名称一致简写,否则 可可以写成别名的方式 ~~~ computed: { // ...mapGetters(['rootMessages']) 简写形式 ...mapGetters({ roots: 'rootMessages' //别名:getter属性 }) } ~~~ 全js ~~~ export const moduleA = { state: () => ({ count: 11, message: '我是A模块' }), mutations: {}, actions: { rootAction({state, rootState}) { console.log("根Message:" + rootState.message + "mokuaiA" + state) }, }, getters: { rootMessages(state, getters, rootState) { console.log("huoquget"); return state.count + rootState.rootMessage } } } ~~~ about.js ~~~ <template> <div class="about"> <h1>This is an about page</h1> <br> <h2> {{this.$store.state.a.message}} </h2> <br> <h2> {{this.$store.state.b.message}} </h2> <h1> <!-- this. 去掉也可以,访问getter1 --> 访问子模块:{{this.$store.getters.rootMessages}} </h1> <br> 访问方法二:{{rootMessages}} <!-- 访问方法二起别名:{{roots}} --> </div> </template> <script> import {mapGetters} from 'vuex' export default { computed: { ...mapGetters(['rootMessages']) //简写形式 // ...mapGetters({ //别名方式 // roots: 'rootMessages' //别名:getter属性 // }) } } </script> ~~~ 由此可以看出,getters是注册到全局空间当中了 ## 3. 模块的名称空间 ### 3.1 案例引出 1. store/index.js ~~~ actions: { saySomething({state,rootState}) { console.log("我是根模块===》根Message:" + state.rootMessage) }, }, ~~~ 2. moduleA/index.js ~~~ actions: { saySomething({state}) { console.log("我是子模块===》根Message:" + rootState.rootMessage + "-子模块值" + state.count) }, ~~~ 根模块和子模块A都有一个action方法 saySomething about.vue ~~~ <button @click="submit">说话</button> <script> import {mapGetters, mapActions} from 'vuex' export default { methods: { ...mapActions(['saySomething']), submit() { this.saySomething(); } } } </script> ~~~ 如下图:当点击按钮时,发现两个方法都被触发了,这就是模块化后,子module与父module都在一个名称空间带来的问题。 ![](https://img.kancloud.cn/bb/2f/bb2f131fe40b9b237c49ec7109c9d8b0_828x300.png) ### **3.2 引入名称空间** * 默认情况下,模块内部的 action、mutation 和 getter 是注册在**全局命名空间**的——这样使得多个模块能够对同一 mutation 或 action 作出响应。 * 如果希望你的模块具有更高的封装度和复用性,你可以通过添加`namespaced: true`的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如上边的问题,可以通过增加名称空间来解决 模块A ![](https://img.kancloud.cn/a6/57/a6576bd3984aea666783688fdbcf0cc5_434x264.png) 此时点击按钮,只会调用父模块的方法 ![](https://img.kancloud.cn/e9/e4/e9e4a4133ffbd6b4d44669e260189efb_816x305.png) #### **3.2.1 那如何调用子组件的action,mution和gettters呢?** 就是调用方法的时候 '模块名/属性名',如: ![](https://img.kancloud.cn/6d/4e/6d4e0e352701533991b5bf376ecebe8a_306x115.png) 1)调用actions dispatch('a/saySomething') 2) 调用getter getters['a/isAdmin'] 3)调用mutations commit('a/add') 完整 模块A ~~~ export const moduleA = { namespaced: true, state: () => ({ count: 11, message: '我是A模块' }), mutations: { add(state){ console.log("mutations - add执行") state.count++; } }, actions: { saySomething() { console.log("action - saySomething执行") }, }, getters: { rootMessages(state, getters, rootState) { console.log("getter 执行"); return state.count + rootState.rootMessage } } } ~~~ about.vue ~~~ <template> <div class="about"> <h1>This is an about page</h1> <br> <h2> 模块一:{{this.$store.state.a.count}} </h2> <br> <h2> {{this.$store.state.b.message}} </h2> <h1> <!-- this. 去掉也可以,访问getter1 --> <!-- 访问子模块:{{this.$store.getters.rootMessages}}--> 访问子模块:{{this.$store.getters['a/rootMessages']}} </h1> <br> 访问方法二:{{rootMessages}} <!-- 访问方法二起别名:{{roots}} --> <button @click="submit">说话</button> </div> </template> <script> import {mapGetters, mapActions} from 'vuex' export default { computed: { ...mapGetters({rootMessages: 'a/rootMessages'}) //简写形式 // ...mapGetters({ //别名方式 // roots: 'rootMessages' //别名:getter属性 // }) }, methods: { ...mapActions(['saySomething']), submit() { // this.saySomething(); this.$store.dispatch('a/saySomething'); this.$store.commit('a/add'); this.$store.getters['a/rootMessages']; } } } </script> ~~~ 点击按钮,触发 ![](https://img.kancloud.cn/d5/c6/d5c62672a2b9bee30271fb41b14367e6_827x333.png) #### 3.2.1 在带命名空间的模块内访问全局内容(Global Assets) 如果你希望使用全局 state 和 getter,`rootState`和`rootGetters`会作为第三和第四参数传入 getter,也会通过`context`对象的属性传入 action。 ~~~ modules: { foo: { namespaced: true, getters: { // 在这个模块的 getter 中,`getters` 被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' }, someOtherGetter: state => { ... } }, actions: { // 在这个模块中, dispatch 和 commit 也被局部化了 // 他们可以接受 `root` 属性以访问根 dispatch 或 commit someAction ({ 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) { ... } } } } ~~~ #### 在带命名空间的模块注册全局 action 若需要在带命名空间的模块注册全局 action,你可添加`root: true`,并将这个 action 的定义放在函数`handler`中。例如: ~~~ { actions: { someOtherAction ({dispatch}) { dispatch('someAction') } }, modules: { foo: { namespaced: true, actions: { someAction: { root: true, handler (namespacedContext, payload) { ... } // -> 'someAction' } } } } } ~~~ #### **3.2.3 带命名空间的绑定函数** 当使用`mapState`,`mapGetters`,`mapActions`和`mapMutations`这些函数来绑定带命名空间的模块时,写起来可能比较繁琐: ~~~ computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b }) }, methods: { ...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() ]) } ~~~ ### **3.3 `createNamespacedHelpers`** ### 3.3.1 子组件中使用模块 * 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数: * 上边调用actons,mutations和getters、state都需要路径,有了createNamespacedHelpers工具,可以帮我们自动创建名称空间,引入空间的mapGetters、mapState、mapMutations和mapActions辅助函数 B模块 ~~~ export const moduleB = { namespaced: true, //开启名称空间 state: () => ({ message: '我来自B模块-_-', count: 100 }), mutations: { add(state){ console.log("mutations B模块 - add执行") state.count++; } }, actions: { saySomething() { console.log("action B模块- saySomething执行") }, }, getters: { rootMessages(state, getters, rootState) { console.log("getter B模块 执行"); return state.count + rootState.rootMessage } } } ~~~ `home.vue` ~~~ <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"/> <h1>B的count:{{count}}</h1> <button @click="submit">B模块说话</button> </div> </template> <script> import {createNamespacedHelpers} from 'vuex' const {mapState, mapActions,mapMutations} = createNamespacedHelpers('b') export default { name: "Home", components: {}, computed: { ...mapState(['count', 'message']) }, methods: { ...mapActions(['saySomething']), ...mapMutations(['add']), submit() { this.saySomething(); this.add(); } } }; </script> ~~~ 注意;createNamespacedHelpers('b')中的b,代表的是父木块中注册的子模块 ![](https://img.kancloud.cn/2c/22/2c22f62842c854798bbe3f64e2d3a141_513x296.png) 点击按钮,count加1,action之类的都执行了 ![](https://img.kancloud.cn/79/5e/795ea71a133118acd26ca864fc25a3e9_841x421.png) #### 3.3.2 引入多个名称空间 例如在上边的例子中,再引入A模块(但是要起别名) home.vue ~~~ <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"/> <h1>B的count:{{count}}</h1> <h1>A的count:{{countA}}</h1> <button @click="submit">B模块说话</button> </div> </template> <script> import {createNamespacedHelpers} from 'vuex' const {mapState, mapActions,mapMutations} = createNamespacedHelpers('b') const {mapState: mapStateA,mapMutations: mapMutationsA} = createNamespacedHelpers('a') export default { name: "Home", components: {}, computed: { ...mapState(['count', 'message']), ...mapStateA({countA: 'count'}) //A模块别名 }, methods: { ...mapActions(['saySomething']), ...mapMutations(['add']), ...mapMutationsA({addA:'add'}), //A模块别名 submit() { this.saySomething(); this.add(); this.addA(); //调用A模块 } } }; </script> ~~~ home页 ![](https://img.kancloud.cn/24/23/2423b1f99a7b9b41a669d207d550f775_770x351.png) about页 ![](https://img.kancloud.cn/dd/23/dd2325a6cc313bea6010455873b902e5_916x378.png) 由上可以看出数据两个页同步了