1
1
# Modules
2
+ 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
2
3
3
- 使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。
4
-
5
- 为了解决以上问题,Vuex 允许我们将 store 分割到** 模块(module)** 。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割:
4
+ 为了解决以上问题,Vuex 允许我们将 store 分割成** 模块(module)** 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
6
5
7
6
``` js
8
7
const moduleA = {
@@ -31,14 +30,14 @@ store.state.b // -> moduleB 的状态
31
30
32
31
### 模块的局部状态
33
32
34
- 对于模块内部的 mutation 和 getter,接收的第一个参数是** 模块的局部状态 ** 。
33
+ 对于模块内部的 mutation 和 getter,接收的第一个参数是** 模块的局部状态对象 ** 。
35
34
36
35
``` js
37
36
const moduleA = {
38
37
state: { count: 0 },
39
38
mutations: {
40
39
increment (state ) {
41
- // state 模块的局部状态
40
+ // 这里的 ` state` 对象是模块的局部状态
42
41
state .count ++
43
42
}
44
43
},
@@ -51,7 +50,7 @@ const moduleA = {
51
50
}
52
51
```
53
52
54
- 同样,对于模块内部的 action,` context.state ` 是局部状态,根节点的状态是 ` context.rootState ` :
53
+ 同样,对于模块内部的 action,局部状态通过 ` context.state ` 暴露出来, 根节点状态则为 ` context.rootState ` :
55
54
56
55
``` js
57
56
const moduleA = {
@@ -66,7 +65,7 @@ const moduleA = {
66
65
}
67
66
```
68
67
69
- 对于模块内部的 getter,根节点状态会作为第三个参数 :
68
+ 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来 :
70
69
71
70
``` js
72
71
const moduleA = {
@@ -81,41 +80,133 @@ const moduleA = {
81
80
82
81
### 命名空间
83
82
84
- 模块内部的 action、mutation、 和 getter 现在仍然注册在 ** 全局命名空间** ——这样保证了多个模块能够响应同一 mutation 或 action。你可以通过添加前缀或后缀的方式隔离各模块,以避免名称冲突。你也可能希望写出一个可复用的模块,其使用环境不可控。例如,我们想创建一个 ` todos ` 模块 :
83
+ 默认情况下, 模块内部的 action、mutation 和 getter 是注册在 ** 全局命名空间** 的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块更加自包含或提高可重用性,你可以通过添加 ` namespaced: true ` 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如 :
85
84
86
85
``` 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 ` 即可。
88
131
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
+ }
93
160
```
94
161
162
+ #### 带命名空间的绑定函数
163
+
164
+ 当使用 ` mapState ` , ` mapGetters ` , ` mapActions ` 和 ` mapMutations ` 这些函数来绑定命名空间模块时,写起来可能比较繁琐:
165
+
95
166
``` 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
+ ```
98
180
99
- // 使用添加了前缀的名称定义 getter、action 和 mutation
100
- const todosModule = {
101
- state: { todos: [] },
181
+ 对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
102
182
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
+ ```
108
197
109
- actions: {
110
- [types .FETCH_ALL ] (context , payload ) {
111
- // ...
112
- }
113
- },
198
+ #### 给插件开发者的注意事项
114
199
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' )
119
210
}
120
211
}
121
212
```
@@ -125,13 +216,40 @@ const todosModule = {
125
216
在 store 创建** 之后** ,你可以使用 ` store.registerModule ` 方法注册模块:
126
217
127
218
``` js
219
+ // 注册模块 `myModule`
128
220
store .registerModule (' myModule' , {
129
221
// ...
130
222
})
223
+ // 注册嵌套模块 `nested/myModule`
224
+ store .registerModule ([' nested' , ' myModule' ], {
225
+ // ...
226
+ })
131
227
```
132
228
133
- 模块的状态将是 ` store.state.myModule ` 。
229
+ 之后就可以通过 ` store.state.myModule ` 和 ` store.state.nested.myModule ` 访问模块的状态 。
134
230
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 结合在一起,实现应用的路由状态管理 。
136
232
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