Переглянути джерело

Allow nested modules (#220)

* Flatten the keys of given object on mergeObjects

* Add utils for nested modules

* Allow nested modules

* Update test cases for nested modules

* Allow nested modules have state and mutations

* Allow nested modules only when it is defined under `modules` key
katashin 9 роки тому
батько
коміт
61ae0be8a2
3 змінених файлів з 196 додано та 44 видалено
  1. 47 8
      src/index.js
  2. 24 2
      src/util.js
  3. 125 34
      test/unit/test.js

+ 47 - 8
src/index.js

@@ -1,4 +1,7 @@
-import { mergeObjects, deepClone, getWatcher } from './util'
+import {
+  mergeObjects, deepClone, isObject,
+  getNestedState, getWatcher
+} from './util'
 import devtoolMiddleware from './middlewares/devtool'
 import devtoolMiddleware from './middlewares/devtool'
 import override from './override'
 import override from './override'
 
 
@@ -142,8 +145,16 @@ class Store {
    */
    */
 
 
   _setupModuleState (state, modules) {
   _setupModuleState (state, modules) {
+    if (!isObject(modules)) return
+
     Object.keys(modules).forEach(key => {
     Object.keys(modules).forEach(key => {
-      Vue.set(state, key, modules[key].state || {})
+      const module = modules[key]
+
+      // set this module's state
+      Vue.set(state, key, module.state || {})
+
+      // retrieve nested modules
+      this._setupModuleState(state[key], module.modules)
     })
     })
   }
   }
 
 
@@ -156,24 +167,52 @@ class Store {
 
 
   _setupModuleMutations (updatedModules) {
   _setupModuleMutations (updatedModules) {
     const modules = this._modules
     const modules = this._modules
-    const allMutations = [this._rootMutations]
     Object.keys(updatedModules).forEach(key => {
     Object.keys(updatedModules).forEach(key => {
       modules[key] = updatedModules[key]
       modules[key] = updatedModules[key]
     })
     })
-    Object.keys(modules).forEach(key => {
+    const updatedMutations = this._createModuleMutations(modules, [])
+    this._mutations = mergeObjects([this._rootMutations, ...updatedMutations])
+  }
+
+  /**
+   * Helper method for _setupModuleMutations.
+   * The method retrieve nested sub modules and
+   * bind each mutations to its sub tree recursively.
+   *
+   * @param {Object} modules
+   * @param {Array<String>} nestedKeys
+   * @return {Array<Object>}
+   */
+
+  _createModuleMutations (modules, nestedKeys) {
+    if (!isObject(modules)) return []
+
+    return Object.keys(modules).map(key => {
       const module = modules[key]
       const module = modules[key]
-      if (!module || !module.mutations) return
+      const newNestedKeys = nestedKeys.concat(key)
+
+      // retrieve nested modules
+      const nestedMutations = this._createModuleMutations(module.modules, newNestedKeys)
+
+      if (!module || !module.mutations) {
+        return mergeObjects(nestedMutations)
+      }
+
       // bind mutations to sub state tree
       // bind mutations to sub state tree
       const mutations = {}
       const mutations = {}
       Object.keys(module.mutations).forEach(name => {
       Object.keys(module.mutations).forEach(name => {
         const original = module.mutations[name]
         const original = module.mutations[name]
         mutations[name] = (state, ...args) => {
         mutations[name] = (state, ...args) => {
-          original(state[key], ...args)
+          original(getNestedState(state, newNestedKeys), ...args)
         }
         }
       })
       })
-      allMutations.push(mutations)
+
+      // merge mutations of this module and nested modules
+      return mergeObjects([
+        mutations,
+        ...nestedMutations
+      ])
     })
     })
-    this._mutations = mergeObjects(allMutations)
   }
   }
 
 
   /**
   /**

+ 24 - 2
src/util.js

@@ -13,9 +13,9 @@ export function mergeObjects (arr) {
         // allow multiple mutation objects to contain duplicate
         // allow multiple mutation objects to contain duplicate
         // handlers for the same mutation type
         // handlers for the same mutation type
         if (Array.isArray(existing)) {
         if (Array.isArray(existing)) {
-          existing.push(obj[key])
+          prev[key] = existing.concat(obj[key])
         } else {
         } else {
-          prev[key] = [prev[key], obj[key]]
+          prev[key] = [existing].concat(obj[key])
         }
         }
       } else {
       } else {
         prev[key] = obj[key]
         prev[key] = obj[key]
@@ -48,6 +48,28 @@ export function deepClone (obj) {
   }
   }
 }
 }
 
 
+/**
+ * Check whether the given value is Object or not
+ *
+ * @param {*} obj
+ * @return {Boolean}
+ */
+
+export function isObject (obj) {
+  return obj !== null && typeof obj === 'object'
+}
+
+/**
+ * Get state sub tree by given keys.
+ *
+ * @param {Object} state
+ * @param {Array<String>} nestedKeys
+ * @return {Object}
+ */
+export function getNestedState (state, nestedKeys) {
+  return nestedKeys.reduce((state, key) => state[key], state)
+}
+
 /**
 /**
  * Hacks to get access to Vue internals.
  * Hacks to get access to Vue internals.
  * Maybe we should expose these...
  * Maybe we should expose these...

+ 125 - 34
test/unit/test.js

@@ -65,20 +65,41 @@ describe('Vuex', () => {
       },
       },
       mutations,
       mutations,
       modules: {
       modules: {
-        one: {
+        nested: {
           state: { a: 2 },
           state: { a: 2 },
-          mutations
+          mutations,
+          modules: {
+            one: {
+              state: { a: 3 },
+              mutations
+            },
+            nested: {
+              modules: {
+                two: {
+                  state: { a: 4 },
+                  mutations
+                },
+                three: {
+                  state: { a: 5 },
+                  mutations
+                }
+              }
+            }
+          }
         },
         },
-        two: {
-          state: { a: 3 },
+        four: {
+          state: { a: 6 },
           mutations
           mutations
         }
         }
       }
       }
     })
     })
     store.dispatch(TEST, 1)
     store.dispatch(TEST, 1)
     expect(store.state.a).to.equal(2)
     expect(store.state.a).to.equal(2)
-    expect(store.state.one.a).to.equal(3)
-    expect(store.state.two.a).to.equal(4)
+    expect(store.state.nested.a).to.equal(3)
+    expect(store.state.nested.one.a).to.equal(4)
+    expect(store.state.nested.nested.two.a).to.equal(5)
+    expect(store.state.nested.nested.three.a).to.equal(6)
+    expect(store.state.four.a).to.equal(7)
   })
   })
 
 
   it('hot reload', function () {
   it('hot reload', function () {
@@ -93,20 +114,41 @@ describe('Vuex', () => {
       },
       },
       mutations,
       mutations,
       modules: {
       modules: {
-        one: {
+        nested: {
           state: { a: 2 },
           state: { a: 2 },
-          mutations
+          mutations,
+          modules: {
+            one: {
+              state: { a: 3 },
+              mutations
+            },
+            nested: {
+              modules: {
+                two: {
+                  state: { a: 4 },
+                  mutations
+                },
+                three: {
+                  state: { a: 5 },
+                  mutations
+                }
+              }
+            }
+          }
         },
         },
-        two: {
-          state: { a: 3 },
+        four: {
+          state: { a: 6 },
           mutations
           mutations
         }
         }
       }
       }
     })
     })
     store.dispatch(TEST, 1)
     store.dispatch(TEST, 1)
     expect(store.state.a).to.equal(2)
     expect(store.state.a).to.equal(2)
-    expect(store.state.one.a).to.equal(3)
-    expect(store.state.two.a).to.equal(4)
+    expect(store.state.nested.a).to.equal(3)
+    expect(store.state.nested.one.a).to.equal(4)
+    expect(store.state.nested.nested.two.a).to.equal(5)
+    expect(store.state.nested.nested.three.a).to.equal(6)
+    expect(store.state.four.a).to.equal(7)
 
 
     // hot reload only root mutations
     // hot reload only root mutations
     store.hotUpdate({
     store.hotUpdate({
@@ -118,34 +160,50 @@ describe('Vuex', () => {
     })
     })
     store.dispatch(TEST, 1)
     store.dispatch(TEST, 1)
     expect(store.state.a).to.equal(1) // only root mutation updated
     expect(store.state.a).to.equal(1) // only root mutation updated
-    expect(store.state.one.a).to.equal(4)
-    expect(store.state.two.a).to.equal(5)
+    expect(store.state.nested.a).to.equal(4)
+    expect(store.state.nested.one.a).to.equal(5)
+    expect(store.state.nested.nested.two.a).to.equal(6)
+    expect(store.state.nested.nested.three.a).to.equal(7)
+    expect(store.state.four.a).to.equal(8)
 
 
     // hot reload modules
     // hot reload modules
     store.hotUpdate({
     store.hotUpdate({
       modules: {
       modules: {
-        one: {
+        nested: {
           state: { a: 234 },
           state: { a: 234 },
-          mutations: {
-            [TEST] (state, n) {
-              state.a += n
+          mutations,
+          modules: {
+            one: {
+              state: { a: 345 },
+              mutations
+            },
+            nested: {
+              modules: {
+                two: {
+                  state: { a: 456 },
+                  mutations
+                },
+                three: {
+                  state: { a: 567 },
+                  mutations
+                }
+              }
             }
             }
           }
           }
         },
         },
-        two: {
-          state: { a: 345 },
-          mutations: {
-            [TEST] (state, n) {
-              state.a -= n
-            }
-          }
+        four: {
+          state: { a: 678 },
+          mutations
         }
         }
       }
       }
     })
     })
     store.dispatch(TEST, 2)
     store.dispatch(TEST, 2)
     expect(store.state.a).to.equal(2)
     expect(store.state.a).to.equal(2)
-    expect(store.state.one.a).to.equal(6) // should not reload initial state
-    expect(store.state.two.a).to.equal(3) // should not reload initial state
+    expect(store.state.nested.a).to.equal(6) // should not reload initial state
+    expect(store.state.nested.one.a).to.equal(7) // should not reload initial state
+    expect(store.state.nested.nested.two.a).to.equal(8) // should not reload initial state
+    expect(store.state.nested.nested.three.a).to.equal(9) // should not reload initial state
+    expect(store.state.four.a).to.equal(10) // should not reload initial state
 
 
     // hot reload all
     // hot reload all
     store.hotUpdate({
     store.hotUpdate({
@@ -155,19 +213,49 @@ describe('Vuex', () => {
         }
         }
       },
       },
       modules: {
       modules: {
-        one: {
+        nested: {
           state: { a: 234 },
           state: { a: 234 },
           mutations: {
           mutations: {
             [TEST] (state, n) {
             [TEST] (state, n) {
-              state.a = n
+              state.a += n
+            }
+          },
+          modules: {
+            one: {
+              state: { a: 345 },
+              mutations: {
+                [TEST] (state, n) {
+                  state.a += n
+                }
+              }
+            },
+            nested: {
+              modules: {
+                two: {
+                  state: { a: 456 },
+                  mutations: {
+                    [TEST] (state, n) {
+                      state.a += n
+                    }
+                  }
+                },
+                three: {
+                  state: { a: 567 },
+                  mutations: {
+                    [TEST] (state, n) {
+                      state.a -= n
+                    }
+                  }
+                }
+              }
             }
             }
           }
           }
         },
         },
-        two: {
-          state: { a: 345 },
+        four: {
+          state: { a: 678 },
           mutations: {
           mutations: {
             [TEST] (state, n) {
             [TEST] (state, n) {
-              state.a = n
+              state.a -= n
             }
             }
           }
           }
         }
         }
@@ -175,8 +263,11 @@ describe('Vuex', () => {
     })
     })
     store.dispatch(TEST, 3)
     store.dispatch(TEST, 3)
     expect(store.state.a).to.equal(-1)
     expect(store.state.a).to.equal(-1)
-    expect(store.state.one.a).to.equal(3)
-    expect(store.state.two.a).to.equal(3)
+    expect(store.state.nested.a).to.equal(9)
+    expect(store.state.nested.one.a).to.equal(10)
+    expect(store.state.nested.nested.two.a).to.equal(11)
+    expect(store.state.nested.nested.three.a).to.equal(6)
+    expect(store.state.four.a).to.equal(7)
   })
   })
 
 
   it('middleware', function () {
   it('middleware', function () {