前言

上一篇文章我们简单介绍了一下 Vuex 的简单使用,但随之也会产生一个问题。由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。尤其是在多人开发中会显得特别不便利。

介绍

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。深入学习可能还是要去看官方文档

语法:

在Vuex的配置中通过modules配置不同的模块,modules中模块的配置语法是模块名:{state,getters,mutations,action,modules}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Vue from 'vue'
import Vuex from 'vuex'
import commend from './commend'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name'root'
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    commend: {
state: {
name: '小明',
age: 18
},
getters: {
//some getters
},
mutation: {
// some mutations
},
actions: {
// some action
},
modules: {
// some modules
}
}
  }
})

虽然在我们在实例化vuex.Store时创建了不同的模块,但是最终生成的store对象所有数据(包含子模块的数据)依然存储在同一个state getters mutations actions中。

命名空间

vuex中的modules支持命名空间,设置了命名空间的模块与没有设置命名空间的模块存在一定的区别。

语法:在需要开启命名空间的module模块中添加一个配置选项,namespaced:true

module state

语法:存放在module中的state,是否开启命名空间使用方式都是一样的。都是通过store.state.模块名.state属性名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let store = new Vuex.Store({
  state: {
    name'root'
  },
  modules: {
    commend: {
state: {
name: '小王'
}
}
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      }
    }
  }
})
// store.state.name => "root"
// store.state.commend.name => "小王"
// store.state.a1.name => "老李头"
// store.state.a1.age => 56

实例:辅助函数mapState获取指定模块的属性,必须使用 "属性名:函数的形式"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
  <div id="app">
    <button @click="showStore">show</button>
    <p>{{name}}</p>
    <p>{{commendName}}</p>
    <p>{{a1Name}}</p>
  </div>
</template>

<script>
import {mapState} from 'vuex'

export default {
  name'App',
  computed: {
    ...mapState(['name']), // $store.state.name  => 'root'
    ...mapState({
      commendNamestate => state.commend.name, // $store.state.commend.name  => '小王'
      a1Name({a1}) => a1.name // $store.state.a1.name  => '老李头'
    })
  },
  methods: {
    showStore() {
      console.log(this.$store)
    }
  }
}
</script>

module getters

语法:所有module中的getter是直接存储在store.getters中的,依然通过store.getters.属性名访问

所以在开发中如果你的模块如果没有开启命名空间,一定要保证未命名模块与未命名模块之间/未命名模块与根getters之间不能有同名getters属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
// store.getters.test
    test(state) {
      return  state.name + '!!'
    }
//ERROR! 注意子模块a1中也有一个同名getter,因为a1没有开启命名空间,所以导致两个getter命名冲突产生报错
 reverseName(state) {
          return state.name.split('').reverse().join('')
    }
  },

  modules: {
    a1: {
      state: {
        name'老李头',
        age56
      },
      getters: {
// store.getters.reverseName
        reverseName(state) {
          return state.name.split('').reverse().join('')
        }
      }
    }
  }
})

注意:当开启了命名空间后module,getters 获取方式发生改变

变为 –>store.getters['模块名/getter属性名']

所以开启命名空间的getter可以与其他模块根store中的getter同名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
// store.getters.test
    test(state) {
      return  state.name + '!!'
    }
//正确, a1模块中虽然getters内包好一个与当前根getter同名的属性,但是因为a1模块开启了命名空间.
// a1的getter最终生成后属性名会发生改变就不会造成命名冲突
 reverseName(state) {
          return state.name.split('').reverse().join('')
    }
  },

  modules: {
    a1: {
namespaced: true,
      state: {
        name'老李头',
        age56
      },
      getters: {
// store.getters.reverseName
        reverseName(state) {
          return state.name.split('').reverse().join('')
        }
      }
    }
  }
})
// store.getters.reverseName 根store的getter => 'toor'
// store.getters['a1/reverseName'] a1模块的getter => "头李老"

实例:辅助函数mapGetter 获取module的getter方法没有变化的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 import { mapGetters} from 'vuex'

export default {
  name'App',
  computed: {
    ...mapGetters(['reverseName']),
    ...mapGetters({a1ReverseName'a1/reverseName'})
  },
  methods: {
    showStore() {
      console.log(this.$store)
    }
  }
}

注意:模块中getters函数包含四个参数

参数一 state ——– 当前模块的state

参数二 getters ——– 当前模块的getters(如果当前模块没有开启命名空间 该参数的值等于参数四)

参数三 rootState ——– 根state store的state

参数四 rootGetter ——– 根getters store的getters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 开启命名空间模块getters函数的参数
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    reverseName(state) {
      return  state.name + '!!'
    }
  },
  modules: {
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      },
      getters: {
        reverseName(state,getters, rootState, rootGetters) {
          console.log('state',state) // {name:"老李头", age: 56}
          console.log('getters',getters) // {reverseName: "头李老"}
          console.log('rootState',rootState) // {name: "root", a1: {name:"老李头", age: 56}}}
          console.log('rootGetters',rootGetters) // {reverseName: "root!!", 'a1/reverseName':"头李老"}
          return state.name.split('').reverse().join('')
        }
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 未开启命名空间模块getters函数的参数
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return  state.name + '!!'
    }
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    a1: {
      state: {
        name'老李头',
        age56
      },
      getters: {
        reverseName(state,getters, rootState, rootGetters) {
          console.log('state',state) // {name:"老李头", age: 56}
          console.log('getters',getters) // {rootReverseName: "root!!", 'reverseName':"头李老"}
          console.log('rootState',rootState) // {name: "root", a1: {name:"老李头", age: 56}}}
          console.log('rootGetters',rootGetters) // {rootReverseName: "root!!", 'reverseName':"头李老"}
          return state.name.split('').reverse().join('')
        }
      }
    }
  }
})

module mutation 和 action

语法:所有mutation和action也是直接存储在store中的,依然通过store.dispatch('mutation名') store.dispatch('action名')触发的 。

在开发中如果你的模块如果没有开启命名空间,mutation与action 在模块与模块之间 或模块与根store之间存在同名mutation / action 是不会造成命名冲突不会报错的,但是如果commit / dispatch 这些同名 mutation / action时他们将都会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return  state.name + '!!'
    }
  },
  mutations: {
    myMutation() {
      console.log('根mutation的 myMutation方法被触发了')
    }
  },
  actions: {
    myAction() {
      console.log('根action的 myAction方法被触发了')
    }
  },
  modules: {
    a1: {
      mutations: {
        myMutation() {
          console.log('模块 a1 mutation 的 myMutation方法被触发了')
        }
      },
      actions: {
        myAction() {
          console.log('模块 a1 action的 myAction方法被触发了')
        }
      }
    }
  }
})

// 触发 $store.commit('myMutation') 时 => '根mutation的 myMutation方法被触发了'
// '模块 a1 mutation 的 myMutation方法被触发了'

// 触发 $store.dispatch('myAction') 时 => '根action的 myAction方法被触发了'
// '模块 a1 action的 myAction方法被触发了'

注意:当开启了命名空间后module,mutation与action方法名会发生改变

变为 –> '模块名/mutation名' '模块名/action名'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return  state.name + '!!'
    }
  },
  mutations: {
    myMutation() {
      console.log('根mutation的 myMutation方法被触发了')
    }
  },
  actions: {
    myAction() {
      console.log('根action的 myAction方法被触发了')
    }
  },
  modules: {
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      },
      mutations: {
        myMutation() {
          console.log('模块 a1 mutation 的 myMutation方法被触发了')
        }
      },
      actions: {
        myAction() {
          console.log('模块 a1 action的 myAction方法被触发了')
        }
      }
    }
  }
})

// $store.commit('myMutation') => '根mutation的 myMutation方法被触发了'
// $store.commit('a1/myMutation') => '模块 a1 mutation 的 myMutation方法被触发了'

// $store.dispatch('myAction') => '根action的 myAction方法被触发了'
// $store.dispatch('a1/myAction') => '模块 a1 action的 myAction方法被触发了'

注意: 模块中mutation函数无论是否开启具名空间依然只包含两个参数

参数一: 当前模块的局部state

参数二: 载荷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return  state.name + '!!'
    }
  },
  mutations: {
    myMutation() {
      console.log('根mutation的 myMutation方法被触发了')
    }
  },
  modules: {
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      },
      mutations: {
        myMutation(state, payload) { // state => { name: '老李头',age: 56}
          console.log('模块 a1 mutation 的 myMutation方法被触发了')
        }
      }
    }
  }
})

注意: 模块中action函数无论是否开启具名空间依然只包含两个参数

参数一: context 与root state比较多了两个属性

参数二: 载荷

1
2
3
4
5
6
commit  // 用来提交mutation的方法
dispatch // 用来分发action的方法
getters // 命名空间开启局部getters 没有开启命名空间 全局getters
rootGetters // 全局getter
rootState // 全局state
state // 当前模块的statestate

注意:命名空间开启的模块action内部 context.commit 提交mutation时dispatch分发action时会自动添加命名空间前缀,从而实现只提交当前模块mutation/action的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return state.name + '!!'
    }
  },
  mutations: {
    myMutation() {
      console.log('根mutation的 myMutation方法被触发了')
    }
  },
  actions: {
    myAction() {
      console.log('根action的 myAction方法被触发了')
    }
  },
  modules: {
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      },
      mutations: {
        myMutation() {
          console.log(arguments,'模块 a1 mutation 的 myMutation方法被触发了')
        }
      },
      actions: {
        myAction(context) {
          context.commit('myMutation')  // 会隐式转化为你context.commit('a1/myMutation') 
          context.dispatch('a1/myAction'// 会隐式转化为你context.dispatch('a1/a1/myAction')
          console.log(arguments,'模块 a1 action的 myAction方法被触发了')
        }
      }
    }
  }
})

注意: 如果你想要在开启命名空间模块的action中使用context提交/分发全局mutation/action请在commit dispatch方法传入第三个参数{root:true}代表调用全局

1
2
3
dispatch('someOtherAction', null, { root: true }) 

commit('someMutation', null, { root: true })

例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export default new Vuex.Store({
  state: {
    name'root'
  },
  getters: {
    rootReverseName(state) {
      return state.name + '!!'
    }
  },
  mutations: {
    myMutation() {
      console.log('根mutation的 myMutation方法被触发了')
    }
  },
  actions: {
    myAction() {
      console.log('根action的 myAction方法被触发了')
    }
  },
  modules: {
    a1: {
      namespacedtrue// 开启命名空间
      state: {
        name'老李头',
        age56
      },
      mutations: {
        myMutation() {
          console.log(arguments,'模块 a1 mutation 的 myMutation方法被触发了')
        }
      },
      actions: {
        myAction(context) {
          context.commit('myMutation',null,{ roottrue })  // 提交根mutation 'myMutation'
          context.dispatch('myAction',null,{ roottrue }) // 分发根action 'myAction'
          console.log(arguments,'模块 a1 action的 myAction方法被触发了')
        }
      }
    }
  }
})

CodeSandbox

CodeSandbox在线代码演示

注意 如遇到CodeSandBox打开失败,请尝试按下图操作,然后分窗口就能一边看代码一边看效果啦
sandbox