对 Vuex Module 的直观理解

开始以前

本篇只是本人对 module 的学习总结和理解以及一些避免坑的意见,不必定能够用在你的项目当中,即便你要使用,建议你先参考官方对比下先后文。
另外,module 是基于 vuex 即 store 状态的模块化管理方案,因此本篇是针对有过 store 使用经验的同窗的一篇仅供参考的我的总结,若是你还不会 store 你得抓紧了! 或者你能够参考 大宏说 老师的《Vuex白话教程第六讲:Vuex的管理员Module(实战篇)》javascript

模块的局部状态

模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:css

const moduleA = {
    state: { count:10 },
    getters:{
        filterCount(state){
            return state.count * 2// 20;
        }
    },
    mutations:{
        add(state){
            state.count * 2// 20;
        }
    },
}
复制代码

action则是经过context.state暴露出来:html

actions:{
    incrementIfOddOnRootSum(context){
        context.state.count // 10
    }
}
复制代码

模块内部访问全局的状态

action 能够经过rootState获取到根节点的状态:vue

actions:{
    incrementIfOddOnRootSum( { state, rootState } ){
        rootState.xx // 根节点的xx
    }
}
复制代码

getter 接受根节点状态是经过第三个参数暴露出来:java

getters:{
    sumWithRootCount(state, getters, rootState){
        //state 是模块内部的状态
        // getters 模块内部的其余getter
        // rootState 是全局的状态
    }
}
复制代码

命名空间的概念

若是模块不使用命名空间的话,默认状况下模块内部的 getter, action 和 mutation是注册在全局全局命名空间的,这样的坏处是:web

store:{
    state:{
        count:18,
    },
    mutations:{
        setCount(state){
            state.count / 2;
            console.log(state.count) // 9
        }
    },
    modules:{
        a:moduleA
    }
}
moduleA:{
    state:{
        count:10,
    },
    mutations:{
        setCount(state){
            state.count * 2;
            console.log(state.count)//20
        }
    }
}
复制代码

在提交 moduleA 的 mutation 时:vuex

this.$store.commit('setCount');
// 猜猜会打印啥?
// 9 和 20  这是由于前面所说的模块内部的getter,action和mutation是注册在全局全局命名空间的。
//因此上面的例子中全局命名空间里有2个名为setCount的mutation,而后他们都被触发了。
复制代码

开启命名空间

想要让模块内部的 getter, mutation , action只做用域当前局部模块内的话能够给模块添加namespaced属性:api

modules:{
    moduleA:{
        namespaced:true,
        state:{...}, //state仍是仅做用于当前模块内部
        getter:{
            isAdmin(){...}
        },
        mutations:{
            login(){...}
        },
        actions:{
            getToken(){...}
        }
    }
}
复制代码

当开启命名空间的模块被注册后它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名,因此触发路径也有所改变:bash

store.getters['moduleA/isAdmin']; // 命名空间模块内部的getter
store.dispatch('moduleA/getToken'); // 命名空间模块内部的action
store.commit('moduleA/login'); // 命名空间模块内部的mutation
复制代码

命名空间模块的嵌套

文档里也有说模块内是还能够嵌套模块的大概意思就是:app

modules:{
    moduleA:{
        state:{...},
        mutations:{...},
        // 嵌套模块
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(state){}//由于嵌套模块没有本身命名空间,因此就自动继承了父命名空间,因此就能够这样触发这个getter:store.getters['moduleA/profile'];
                }
            },
            // 进一步嵌套命名空间
            posts:{
                namespaced:true,//开启命名空间
                state:{...},
                getters:{
                    popular(){...}//前面咱们说过,开启命名空间的模块它全部的getter、action、mutation都会自动根据模块的路径调整命名 -> store.getters['moduleA/posts/popular']
                }
            }
        }
    }
}
复制代码

嵌套模块继承命名空间后带来的问题

接着上面继承命名空间的例子:

modules:{
    moduleA:{
        state:{...},
        mutations:{...},
        // 嵌套模块
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(){...}
                }
            }
        }
    }
}
复制代码

若是我如今要触发profile这个getter我能够这样:

store.getters['moduleA/profile'];
复制代码

由于即便是嵌套的模块但mypage没有本身的命名空间因此继承了父命名空间,因此这样触发看上去没有问题。

问题来了⚠️

若是父命名空间内也有一个名为 profile 的getter:

modules:{
    moduleA:{
        state:{...},
        getters:{
            profile(state){...}
        }
        mutations:{...},
        // 嵌套模块
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(){...}
                }
            }
        }
    }
}
复制代码

这个时候若是再执行:

store.getters['moduleA/profile'];
复制代码

会是什么结果呢?你们能够本身动手试一试,加深一下印象。

带命名空间的模块和全局命名空间模块的一些互动

获取或修改全局状态:

若是你但愿使用全局 state 和 getter,rootState 和 rootGetters 会做为第三和第四参数传入 getter,也会经过 context 对象的属性传入 action。

modules: {
      foo: {
        namespaced: true,
        getters:{
            someGetter(state, getters, rootState, rootGettrers){
                // state 是当前模块内部是状态
                // getters 是当前模块内部的 getters
                // rootState 是全局下的状态
                // rootGettrers 是全局下的 gettrers
            }
        },
        actions:{
            someAction( { getters, rootGetters}){
                // getters 当前模块内部的 getters,
                // rootGettrers 是全局下的 gettrers
            }
        }
      }
 }
复制代码

若须要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 做为第三参数传给 dispatch 或 commit 便可:

action:{
    someAction( { dispatch, commit}){
        dispatch('someOtherAction');// 分发当前模块内名为someOtherAction的action
        dispatch('someOtherAction'null, { root: true })// 分发全局名为someOtherAction的action
        commit('someMutation'// 提交当前模块内名为someMutation的mutation
        commit('someMutation'null, { root: true }) // 提交全局名为someMutation的mutation
    }
}
复制代码

是的访问全局只须要提供rootStaterootGetters参数就好,而分发action或提交mutation只须要将 { root: true }做为第三个参数就好。

将模块内部的action注册到全局:

若业务须要在带命名空间的模块中注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
    actions: {
        someOtherAction ({dispatch}) {
            dispatch('someAction')
        }
    },
    modules: {
        foo: {
        namespaced: true,
        actions: {
            someAction: {
                  root: true,
                  handler (namespacedContext, payload) { ... } // -> 'someAction'
                }
            }
        }
    }
}
复制代码

在组件中使用带命名空间的模块

官方给了两种方案,这里我使用命名空间辅助函数的写法,另外一种写法有兴趣的同窗能够去参考一下

这个例子官方其实已经给的很简洁直观了因此咱们直接看:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module`上下文中 中查找
    ...mapState({
      astate => state.a,
      bstate => state.b
    })
  },
  methods: {
    // 在 `some/nested/module`上下文中 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}
复制代码

这个例子我有一个疑问:若是mapStatemapActions是基于'some/nested/module'上下文的话,那若是我这个组件内还须要使用其余命名空间的模块该怎么办呢?有的同窗可能会说:

    const { mapState, mapActions } = createNamespacedHelpers('otherSome/nested/module')
复制代码

再定义一个上下文不就行了吗?但两个上下文返回的都是一样的mapStatemapActions,我在使用mapActions时,你怎么知道我是在那个上下文中查找呢?

后来想了想我这个疑问是否成立?由于我以为一个模块store应该始终是效力于一个功能组件的。可是不保证没有极端的状况出现,若是真有这种需求的话,该怎么实现?有经验的同窗能够教我一下。

动态注册命名空间模块

若是有业务需求须要咱们动态注册模块,咱们可使用 store.registerModule 方法注册模块:

// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested''myModule'], {
  // ...
})
复制代码

动态注册的前提是你的Store已经被你建立了。

以后就能够经过store.state.myModulestore.state.nested.myModule访问模块的状态。

卸载动态模块

以后不须要模块时可使用store.unregisterModule(moduleName)来动态卸载模块来保证性能

注意:不能使用此方法卸载静态模块(即建立 store 时声明的模块)

模块的复用

模块重用,官方给的场景是:
一、建立多个store,他们共用同一个模块
二、在一个store 中屡次注册同一个模块
说的可能比较抽象,咱们来一个简单的例子:

moduleA.js

const moduleA = {
    state:{
        count:10
    },
    mutations:{
        changeCount(state){
            state.count = 20;
        }
    }
}
export default moduleA;
复制代码

store.js

const store = new Vuex.Store({
    state:{...}
    mutations:{...},
    modules:{
        a:moduleA,
        b:moduleA,
        c:moduleA
    }
})
复制代码

此时modules对象里的a b c 都引用自moduleA模块;
咱们再来建3个组件,而后分别引用a b c这3个模块的实例:

test1.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('a')
    export default {
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
复制代码

test2.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('b')
    export default {
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
复制代码

test3.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('c')
    export default {
            computed: {
                ...mapState({
                name:state => state.count
            })
        }
    }
</script>
复制代码

3个组件的count都等于10,由于modulea b c 都引用自moduleA模块;
此时,若是咱们在test1组件里提交moduleA的mutation:

test1.vue

<template>
    <div>
        {{count}}
         <input type="button" value="click" @click="changeCount">

    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState,mapMutations } = createNamespacedHelpers('a')
    export default {
        methods:{
            ...mapMutations([changeCount])
        }
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
复制代码

此时,只要咱们一提交changeCount,test1 test2 test3 组件里的count都会被改成20;
缘由:

当一个模块被定义,模块可能被用来建立多个实例,这时若是模块还是一个纯粹的对象,则全部实例将共享引用同一个数据对象!这就是模块间数据互相污染的问题。

解决模块复用带来的互相污染问题

为了解决互相污染咱们可使用一个函数声明来返回模块的状态:

const MyReusableModule = {
  state () {
    return {
      count: 10
    }
  },
  // mutation, action 和 getter 等等...
}
复制代码

经过为 state 声明一个初始数据对象的函数,且每次建立一个新实例后,咱们可以调用 state 函数,从而返回初始数据的一个全新副本数据对象。

此番借鉴Vue 组件内的 data
大体意思就是让state以函数声明式返回状态,这样无论模块被实例化多少次,每次实例化时模块内部的state都会是一个全新的函数返回。

最后

给某些同窗一些建议:作笔记、总结这种东西必定是要你本身先学习一遍,而后理解事后的记录,并不是是把人家文档的东西循序渐进放到你的笔记当中,这样作的意义何在呢?骗点击的沙雕网友咱们就不评价了,并且人家文档的东西始终是最新的,并且也有持续更新。复制 - 粘贴 - 发布的这类沙雕网友拜托大家不要浪费你们的时间了。净化学习环境从我作起!

固然一篇总结老是避免不了会用到原文档的一些例子。

相关文章
相关标签/搜索