11# Modules
2+ 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
23
3- 使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。
4-
5- 为了解决以上问题,Vuex 允许我们将 store 分割到** 模块(module)** 。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割:
4+ 为了解决以上问题,Vuex 允许我们将 store 分割成** 模块(module)** 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
65
76``` js
87const moduleA = {
@@ -31,14 +30,14 @@ store.state.b // -> moduleB 的状态
3130
3231### 模块的局部状态
3332
34- 对于模块内部的 mutation 和 getter,接收的第一个参数是** 模块的局部状态 ** 。
33+ 对于模块内部的 mutation 和 getter,接收的第一个参数是** 模块的局部状态对象 ** 。
3534
3635``` js
3736const moduleA = {
3837 state: { count: 0 },
3938 mutations: {
4039 increment (state ) {
41- // state 模块的局部状态
40+ // 这里的 ` state` 对象是模块的局部状态
4241 state .count ++
4342 }
4443 },
@@ -51,7 +50,7 @@ const moduleA = {
5150}
5251```
5352
54- 同样,对于模块内部的 action,` context.state ` 是局部状态,根节点的状态是 ` context.rootState ` :
53+ 同样,对于模块内部的 action,局部状态通过 ` context.state ` 暴露出来, 根节点状态则为 ` context.rootState ` :
5554
5655``` js
5756const moduleA = {
@@ -66,7 +65,7 @@ const moduleA = {
6665}
6766```
6867
69- 对于模块内部的 getter,根节点状态会作为第三个参数 :
68+ 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来 :
7069
7170``` js
7271const moduleA = {
@@ -81,41 +80,133 @@ const moduleA = {
8180
8281### 命名空间
8382
84- 模块内部的 action、mutation、 和 getter 现在仍然注册在 ** 全局命名空间** ——这样保证了多个模块能够响应同一 mutation 或 action。你可以通过添加前缀或后缀的方式隔离各模块,以避免名称冲突。你也可能希望写出一个可复用的模块,其使用环境不可控。例如,我们想创建一个 ` todos ` 模块 :
83+ 默认情况下, 模块内部的 action、mutation 和 getter 是注册在 ** 全局命名空间** 的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块更加自包含或提高可重用性,你可以通过添加 ` namespaced: true ` 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如 :
8584
8685``` js
87- // types.js
86+ const store = new Vuex.Store ({
87+ modules: {
88+ account: {
89+ namespaced: true ,
90+ // 模块内容(module assets)
91+ state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
92+ getters: {
93+ isAdmin () { ... } // -> getters['account/isAdmin']
94+ },
95+ actions: {
96+ login () { ... } // -> dispatch('account/login')
97+ },
98+ mutations: {
99+ login () { ... } // -> commit('account/login')
100+ },
101+ // 嵌套模块
102+ modules: {
103+ // 继承父模块的命名空间
104+ myPage: {
105+ state: { ... },
106+ getters: {
107+ profile () { ... } // -> getters['account/profile']
108+ }
109+ },
110+ // 进一步嵌套命名空间
111+ posts: {
112+ namespaced: true ,
113+ state: { ... },
114+ getters: {
115+ popular () { ... } // -> getters['account/posts/popular']
116+ }
117+ }
118+ }
119+ }
120+ }
121+ })
122+ ```
123+
124+ 启用了命名空间的 getter 和 action 会收到局部化的 ` getter ` ,` dispatch ` 和 ` commit ` 。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 ` namespaced ` 属性后不需要修改模块内的代码。
125+
126+ #### 在命名空间模块内访问全局内容(Global Assets)
127+
128+ 如果你希望使用全局 state 和 getter,` rootState ` 和 ` rootGetter ` 会作为第三和第四参数传入 getter,也会通过 ` context ` 对象的属性传入 action。
129+
130+ 若需要在全局命名空间内分发 action 或提交 mutation,将 ` { root: true } ` 作为第三参数传给 ` dispatch ` 或 ` commit ` 即可。
88131
89- // 定义 getter、action、和 mutation 的名称为常量,以模块名 `todos` 为前缀
90- export const DONE_COUNT = ' todos/DONE_COUNT'
91- export const FETCH_ALL = ' todos/FETCH_ALL'
92- export const TOGGLE_DONE = ' todos/TOGGLE_DONE'
132+ ``` js
133+ modules: {
134+ foo: {
135+ namespaced: true ,
136+ getters: {
137+ // 在这个模块的 getter 中,`getters` 被局部化了
138+ // 你可以使用 getter 的第四个参数来调用 `rootGetters`
139+ someGetter (state , getters , rootState , rootGetters ) {
140+ getters .someOtherGetter // -> 'foo/someOtherGetter'
141+ rootGetters .someOtherGetter // -> 'someOtherGetter'
142+ },
143+ someOtherGetter : state => { ... }
144+ },
145+ actions: {
146+ // 在这个模块中, dispatch 和 commit 也被局部化了
147+ // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
148+ someAction ({ dispatch, commit, getters, rootGetters }) {
149+ getters .someGetter // -> 'foo/someGetter'
150+ rootGetters .someGetter // -> 'someGetter'
151+ dispatch (' someOtherAction' ) // -> 'foo/someOtherAction'
152+ dispatch (' someOtherAction' , null , { root: true }) // -> 'someOtherAction'
153+ commit (' someMutation' ) // -> 'foo/someMutation'
154+ commit (' someMutation' , null , { root: true }) // -> 'someMutation'
155+ },
156+ someOtherAction (ctx , payload ) { ... }
157+ }
158+ }
159+ }
93160```
94161
162+ #### 带命名空间的绑定函数
163+
164+ 当使用 ` mapState ` , ` mapGetters ` , ` mapActions ` 和 ` mapMutations ` 这些函数来绑定命名空间模块时,写起来可能比较繁琐:
165+
95166``` js
96- // modules/todos.js
97- import * as types from ' ../types'
167+ computed: {
168+ ... mapState ({
169+ a : state => state .some .nested .module .a ,
170+ b : state => state .some .nested .module .b
171+ })
172+ },
173+ methods: {
174+ ... mapActions ([
175+ ' some/nested/module/foo' ,
176+ ' some/nested/module/bar'
177+ ])
178+ }
179+ ```
98180
99- // 使用添加了前缀的名称定义 getter、action 和 mutation
100- const todosModule = {
101- state: { todos: [] },
181+ 对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
102182
103- getters: {
104- [types .DONE_COUNT ] (state ) {
105- // ...
106- }
107- },
183+ ``` js
184+ computed: {
185+ ... mapState (' some/nested/module' , {
186+ a : state => state .a ,
187+ b : state => state .b
188+ })
189+ },
190+ methods: {
191+ ... mapActions (' some/nested/module' , [
192+ ' foo' ,
193+ ' bar'
194+ ])
195+ }
196+ ```
108197
109- actions: {
110- [types .FETCH_ALL ] (context , payload ) {
111- // ...
112- }
113- },
198+ #### 给插件开发者的注意事项
114199
115- mutations: {
116- [types .TOGGLE_DONE ] (state , payload ) {
117- // ...
118- }
200+ 如果你开发的[ 插件(Plugin)] ( plugins.md ) 提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称:
201+
202+ ``` js
203+ // 通过插件的参数对象得到空间名称
204+ // 然后返回 Vuex 插件函数
205+ export function createPlugin (options = {}) {
206+ return function (store ) {
207+ // 把空间名字添加到插件模块的类型(type)中去
208+ const namespace = options .namespace || ' '
209+ store .dispatch (namespace + ' pluginAction' )
119210 }
120211}
121212```
@@ -125,13 +216,40 @@ const todosModule = {
125216在 store 创建** 之后** ,你可以使用 ` store.registerModule ` 方法注册模块:
126217
127218``` js
219+ // 注册模块 `myModule`
128220store .registerModule (' myModule' , {
129221 // ...
130222})
223+ // 注册嵌套模块 `nested/myModule`
224+ store .registerModule ([' nested' , ' myModule' ], {
225+ // ...
226+ })
131227```
132228
133- 模块的状态将是 ` store.state.myModule ` 。
229+ 之后就可以通过 ` store.state.myModule ` 和 ` store.state.nested.myModule ` 访问模块的状态 。
134230
135- 模块动态注册功能可以让其他 Vue 插件为了应用的 store 附加新模块,以此来分割 Vuex 的状态管理 。例如,[ ` vuex-router-sync ` ] ( https://github.com/vuejs/vuex-router-sync ) 插件可以集成 vue-router 与 vuex,管理动态模块的路由状态 。
231+ 模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态 。例如,[ ` vuex-router-sync ` ] ( https://github.com/vuejs/vuex-router-sync ) 插件就是通过动态注册模块将 vue-router 和 vuex 结合在一起,实现应用的路由状态管理 。
136232
137- 你也可以使用 ` store.unregisterModule(moduleName) ` 动态地卸载模块。注意,你不能使用此方法卸载静态模块(在创建 store 时声明的模块)。
233+ 你也可以使用 ` store.unregisterModule(moduleName) ` 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。
234+
235+ ### 模块重用
236+
237+ 有时我们可能需要创建一个模块的多个实例,例如:
238+
239+ - 创建多个 store,他们公用同一个模块
240+ - 在一个 store 中多次注册同一个模块
241+
242+ 如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时会 store 或模块间数据互相污染的问题。
243+
244+ 实际上这和 Vue 组件内的 ` data ` 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
245+
246+ ``` js
247+ const MyReusableModule = {
248+ state () {
249+ return {
250+ foo: ' bar'
251+ }
252+ },
253+ // mutation, action 和 getter 等等...
254+ }
255+ ```
0 commit comments