什么是module?
背景:在Vue中State使用是单一状态树结构,应该的所有的状态都放在state里面,如果项目比较复杂,那state是一个很大的对象,store对象也将对变得非常大,难于管理。
module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
怎么用module?
一般结构
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB}) |
模块内部的数据:①内部state,模块内部的state是局部的,也就是模块私有的,比如是car.js模块state中的list数据,我们要通过this.$store.state.car.list获取;②内部getter、mutation和action,仍然注册在全局命名空间内,这是为了多模块可以同时响应同一mutation;this.$store.state.car.carGetter的结结果是undefined,而通过this.$store.state.carGetter则可以拿到。
传参:getters====({state(局部状态),getters(全局getters对象),roosState(根状态)});actions====({state(局部状态),commit,roosState(根状态)}).
新建一个项目体验一下,通过vue –cli新建一个项目vuemodule, 不要忘记安装vuex.
1, 在src 目录下新一个login文件夹,在里面新建index.js 用于存放login 模块的状态。 为了简单起见,我把模块下的state, actions,mutations, getters 全放在index.js文件中。
先简单给它增加一个状态,userName: “sam”
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | const state = { useName: "sam" }; const mutations = { }; const actions = { }; const getters = { }; // 不要忘记把state, mutations等暴露出去。 export default { state, mutations, actions, getters } |
2,在src 目录下,再新建一个 store.js 这是根store, 它通过modules 属性引入 login模块。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import Vue from "vue" ; import Vuex from "Vuex" ; Vue.use(Vuex); // 引入login 模块 import login from "./login" export default new Vuex.Store({ // 通过modules属性引入login 模块。 modules: { login: login } }) |
3, 在main.js中引入store, 并注入到vue 根实例中。
?
1 2 3 4 5 6 7 8 9 10 11 | import Vue from 'vue' import App from './App.vue' // 引入store import store from "./store" new Vue({ el: '#app' , store, // 注入到根实例中。 render: h => h(App) }) |
4,在 app.vue 中通过computed属性获取到login下的state. 这里要注意,在没有modules 的情况下,组件中通过 this.$store.state.属性名 可以获取到,但是有modules 之后,state 被限制到login 的命名空间(模块)下,所以属性名前面必须加模块名(命名空间),组件中通过 this.$store.state.模块名.属性名,在这里是 this.$store.state.login.userName
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <template> <div id= "app" > <img src= "./assets/logo.png" > <h1>{{useName}}</h1> </div> </template> <script> export default { // computed属性,从store 中获取状态state,不要忘记login命名空间。 computed: { useName: function () { return this .$store.state.login.useName } } } </script> |
组件中成功获取到状态。项目目录和展示如下
4 ,通过actions, mutations
改变名字, 这就涉及到dispatch action, commit mutations, mutations 改变state.
先在login 文件夹 index.js中添加changeName action 和 CHANGE_NAME mutations.
?
1 2 3 4 5 6 7 8 9 10 11 | const mutations = { CHANGE_NAME (state, anotherName) { state.useName = anotherName; } }; const actions = { changeName ({commit},anotherName) { commit( "CHANGE_NAME" , anotherName) } }; |
在app.vue 中添加一个按钮:<button> change to json</button>, 点击时,dispatch 一个 action. 那在组件中怎么dispatch actions 呢?
在模块中,state 是被限制到模块的命名空间下,需要命名空间才能访问。 但actions 和mutations, 其实还有 getters 却没有被限制,在默认情况下,它们是注册到全局命名空间下的,所谓的注册到全局命名空间下,其实就是我们访问它们的方式和原来没有module 的时候是一样的。比如没有module 的时候,this.$store.dispatch(“actions”), 现在有了modules, actions 也写在了module 下面(changeName 写到了login目录下的index.js中),我们仍然可以这么写,this.$store.dispatch(“changeName”), 组件中的getters, 也是通过 this.$store.getters.module中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 | <template> <div id= "app" > <img src= "./assets/logo.png" > <h1>{{useName}}</h1> <!-- 添加按钮 --> <div> <button @click= "changeName" > change to json</button> </div> </div> </template> <script> export default { // computed属性,从store 中获取状态state,不要忘记login命名空间。 computed: { useName: function () { return this .$store.state.login.useName } }, methods: { // 和没有modules的时候一样,同样的方式dispatch action changeName() { this .$store.dispatch( "changeName" , "Jason" ) } } } |
5, 局部参数
虽然dispatch action和 commit mutations 可以全局使用,但是写在module 中的actions, mutations 和getters, 它们获得的默认参数却不是全局的,都是局部的,被限定在它们所在的模块中的。比如mutations和getters 会获得state 作为第一个默认参数,这个state参数,就是限定在mutations 和getters 所在模块的state对象,login 文件夹下的mutations 和getters 只会获取到当前index.js 中的 state 作为参数 。 actions 会获得一个context 对象作为参数,这个context 对象就是当前module 的实例,module 相当于一个小store.
那么怎样才能获取到根store 中的state 和 getters 呢? Vuex 提供了 rootState, rootGetters 作为module 中 getters 中默认参数, actions中context 对象,也会多了两个属性,context.getters, context. rootState, 这些全局的默认参数,都排在局部参数的后面。
我们在store.js中添加 state, getters:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | export default new Vuex.Store({ // 通过modules属性引入login 模块。 modules: { login: login }, // 新增state, getters state: { job: "web" }, getters: { jobTitle (state){ return state.job + "developer" } } }) |
login 目录下的 index.js
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const actions = { // actions 中的context参数对象多了 rootState 参数 changeName ({commit, rootState},anotherName) { if (rootState.job == "web" ) { commit( "CHANGE_NAME" , anotherName) } } }; const getters = { // getters 获取到 rootState, rootGetters 作为参数。 // rootState和 rootGetter参数顺序不要写反,一定是state在前,getter在后面,这是vuex的默认参数传递顺序, 可以打印出来看一下。 localJobTitle (state,getters,rootState,rootGetters) { console.log(rootState); console.log(rootGetters); return rootGetters.jobTitle + " aka " + rootState.job } }; |
app.vue 增加h2 展示 loacaJobTitle, 这个同时证明了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 31 32 33 | <template> <div id= "app" > <img src= "./assets/logo.png" > <h1>{{useName}}</h1> <!-- 增加h2 展示 localJobTitle --> <h2>{{localJobTitle}}</h2> <!-- 添加按钮 --> <div> <button @click= "changeName" > change to json</button> </div> </div> </template> <script> import {mapActions, mapState,mapGetters} from "vuex" ; export default { // computed属性,从store 中获取状态state,不要忘记login命名空间。 computed: { ...mapState({ useName: state => state.login.useName }), // mapGeter 直接获得全局注册的getters ...mapGetters([ "localJobTitle" ]) }, methods: { changeName() { this .$store.dispatch( "changeName" , "Jason" ) } } } </script> |
6, 其实actions, mutations, getters, 也可以限定在当前模块的命名空间中。只要给我们的模块加一个namespaced 属性:
?
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 | const state = { useName: "sam" }; const mutations = { CHANGE_NAME (state, anotherName) { state.useName = anotherName; } }; const actions = { changeName ({commit, rootState},anotherName) { if (rootState.job == "web" ) { commit( "CHANGE_NAME" , anotherName) } }, alertName({state}) { alert(state.useName) } }; const getters = { localJobTitle (state,getters,rootState,rootGetters) { return rootGetters.jobTitle + " aka " + rootState.job } }; // namespaced 属性,限定命名空间 export default { namespaced: true , state, mutations, actions, getters } |
当所有的actions, mutations, getters 都被限定到模块的命名空间下,我们dispatch actions, commit mutations 都需要用到命名空间。如 dispacth("changeName"), 就要变成 dispatch("login/changeName"); getters.localJobTitle 就要变成 getters["login/localJobTitle"]
app.vue 如下:
?
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 | <template> <div id= "app" > <img src= "./assets/logo.png" > <h1 @click = "alertName" >{{useName}}</h1> <!-- 增加h2 展示 localJobTitle --> <h2>{{localJobTitle}}</h2> <!-- 添加按钮 --> <div> <button @click= "changeName" > change to json</button> </div> </div> </template> <script> import {mapActions, mapState,mapGetters} from "vuex" ; export default { // computed属性,从store 中获取状态state,不要忘记login命名空间。 computed: { ...mapState( "login" ,{ useName: state => state.useName }), localJobTitle() { return this .$store.getters[ "login/localJobTitle" ] } }, methods: { changeName() { this .$store.dispatch( "login/changeName" , "Jason" ) }, alertName() { this .$store.dispatch( "login/alertName" ) } } } </script> |
有了命名空间之后,mapState, mapGetters, mapActions 函数也都有了一个参数,用于限定命名空间,每二个参数对象或数组中的属性,都映射到了当前命名空间中。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <script> import {mapActions, mapState,mapGetters} from "vuex" ; export default { computed: { // 对象中的state 和数组中的localJobTitle 都是和login中的参数一一对应。 ...mapState( "login" ,{ useName: state => state.useName }), ...mapGetters( "login" , [ "localJobTitle" ]) }, methods: { changeName() { this .$store.dispatch( "login/changeName" , "Jason" ) }, ...mapActions( 'login' , [ 'alertName' ]) } } </script> |