挑战全网最幽默的Vuex系列教程:第六讲 Vuex的管理员Module(实战篇)

写在前面

这一讲是 Vuex 基础篇的最后一讲,也是最为复杂的一讲。若是按照官方来的话,对于新手可能有点难以接受,因此想了下,决定干脆多花点时间,用一个简单的例子来说解,顺便也复习一下以前的知识点。html

首先仍是得先了解下 Module 的背景。咱们知道,Vuex 使用的是单一状态树,应用的全部状态会集中到一个对象中。若是项目比较大,那么相应的状态数据确定就会更多,这样的话,store 对象就会变得至关的臃肿,很是难管理。前端

这就比如一家公司只有老板一我的来管理同样。若是小公司倒还好,公司要是稍微大一点,那就麻烦了。这个时候,老板就会成立各大部门,并给各大部门安排一个主管,把管理的任务分派下去,而后有什么事情须要处理的话,只须要跟这几个主管沟通,由主管再把任务分配下去就好了,这就大大提升了工做效率,也减轻了老板的负担。vue

那么一样的道理,Module 其实就承担了部门管理员的角色,而 store 就是老板。理解了这一层,那么后面就好办多了,接下来,我们就一步一步动起手来开始实践。vue-router

1、准备工做 这里咱们使用官方提供的 vue-cli 来建一个项目「vuex-test」。固然,先得安装 vue-cli:vuex

npm install -g @vue/cli
# OR
yarn global add @vue/cli
复制代码

安装完成后,就可使用如下命令来建立项目了:vue-cli

vue create vuex-test
复制代码

还可使用图形化界面来建立:npm

vue ui
复制代码

具体关于 vue-cli 的使用方法能够去官方查看,戳此进入 bash

项目建立完成之后,找到项目安装的目录,并打开控制台执行:架构

// 先定位到项目的目录下
cd vuex-test

// 而后安装 vuex
npm install vuex --save

// 运行一下
npm run serve
复制代码

运行完成,能够打开 http://localhost:8080/ 看下效果。app

最后你们找一个本身比较比较喜欢的 IDE 来打开这个项目,方便查看和编辑。我我的比较喜欢用 WebStore,这里也推荐给你们。

2、简单入手

项目的默认结构图

这里,咱们只看 src 目录,其余的暂时无论。组件 components 不在这一讲的范围内,因此也能够忽视,资源 assets 也没什么可说的,就是若是有图片或者视频什么的话,都放在这个文件夹里面就是了。

咱们打开 App.vue 文件,去掉组件的相关代码,并编写一点简单的 vue 代码。修改以下:

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{name}}</h1>
        <button @click="modifyNameAction">修更名字</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: 'Lucy'
            }
        },

        methods: {
            modifyNameAction() {
                this.name = "bighone"
            }
        }
    }
</script>
复制代码

如今咱们引入 Vuex ,用它来管理状态数据,好比这里的 name。首先在 src 中新建一个 store.js 文件,并写下以下熟悉的代码:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        name: 'Lucy',
    },
    mutations: {
        setName(state, newName) {
            state.name = newName;
        }
    },
    actions: {
        modifyName({commit}, newName) {
            commit('setName', newName);
        }
    }
});
复制代码

而后,在 main.js 中导入 store,并全局注入:

import store from './store';
// ...
new Vue({
    store,
    render: h => h(App),
}).$mount('#app')
复制代码

最后修改 App.vue 中的代码以下:

<script>
    import {mapState, mapActions} from 'vuex';
    export default {
        computed: {
            ...mapState(['name'])
        },

        methods: {
            ...mapActions(['modifyName']),

            modifyNameAction() {
                this.modifyName('bighone');
            }
        },
    }
</script>
复制代码

想必弄懂这些代码,应该都是没啥问题的,由于这些都是 Vuex 很基础的知识点,这里实操来简单回顾一下,加深印象。若是看不懂,那证实以前的基础知识还没掌握。

3、引入 Module

在前言里面,咱们已经了 Module 的基本职责,那么具体如何使用呢?

Vuex 容许咱们将 store 分割成大大小小的对象,每一个对象也都拥有本身的 state、getter、mutation、action,这个对象咱们把它叫作 module(模块),在模块中还能够继续嵌套子模块、子子模块 ……

如今在 src 里面建个文件夹,命名为 module,而后再里面新建一个 moduleA.js 文件,并编写以下代码:

export default {
    state: {
        text: 'moduleA'
    },
    getters: {},
    mutations: {},
    actions: {}
}
复制代码

如上,再建一个 moduleB.js 文件,这里就不重复了。

而后打开 store.js 文件,导入这两个 module

import moduleA from './module/moduleA';
import moduleB from './module/moduleB';

export default new Vuex.Store({
    modules: {
        moduleA, moduleB,
    },
    // ...
}
复制代码

这个时候,store 中已经注入了两个子模块 moduleA moduleB,咱们能够在 App.vue 中经过 this.$store.state.moduleA.text 这种方式来直接访问模块中的 state 数据。以下修改:

// ...
computed: {
    ...mapState({
        name: state => state.moduleA.text
    }),
},
// ...
复制代码

由此可知,模块内部的 state 是局部的,只属于模块自己全部,因此外部必须经过对应的模块名进行访问。

可是注意了:

模块内部的 action、mutation 和 getter 默承认是注册在全局命名空间的,这样使得多个模块可以对同一 mutation 或 action 做出响应。

这里以 mutation 的响应为例,给 moduleA 和 moduleB 分别新增一个 mutations,以下:

mutations: {
    setText(state) {
        state.text = 'A'
    }
},
复制代码

moduleB 和上面同样,把文本名称修改一下便可,这里就不重复了。而后回到 App.vue 中,修改以下:

<script>
    import {mapState, mapMutations} from 'vuex';
    export default {
        computed: {
            ...mapState({
                name: state => (state.moduleA.text + '和' + state.moduleB.text)
            }),
        },
        methods: {
            ...mapMutations(['setText']),
            modifyNameAction() {
                this.setText();
            }
        },
    }
</script>
复制代码

运行而后点击修改,咱们会发现模块 A 和 B 中的 text 值都改变了。固然,action 的用法如出一辙,你们也能够试试。

若是模块之间的数据有交集的话,那么咱们其实就能够经过这种方式,来同步更新模块之间的数据,虽然看起来很是的方便,可是用的时候可必定要谨慎,这种处理方式一旦没用好,遇到错误,排查起来仍是比较有难度的。

4、访问根节点

咱们已经知晓,模块内部的 state 是局部的,只属于模块自己全部。那么若是咱们要想在模块中访问 store 根节点的数据 state,怎么办呢?

很简单,咱们能够在模块内部的 getter 和 action 中,经过 rootState 这个参数来获取。接下来,咱们给 modelA.js 文件添加一点代码。

export default {
    // ...
    getters: {
        // 注意:rootState必须是第三个参数
        detail(state, getters, rootState) {
            return state.text + '-' + rootState.name;
        }
    },
    actions: {
        callAction({state, rootState}) {
            alert(state.text + '-' + rootState.name);
        }
    }
}
复制代码

而后修改 App.vue

<script>
    import {mapActions, mapGetters} from 'vuex';
    export default {
        computed: {
            ...mapGetters({
                name: 'detail'
            }),
        },
        methods: {
            ...mapActions(['callAction']),
            modifyNameAction() {
                this.callAction();
            }
        },
    }
</script>
复制代码

而后运行你会发现,根节点的数据已经被咱们获取到了。这里须要注意的是在 getters 中,rootState 是以第三个参数暴露出来的,另外,还有第四个参数 rootGetters,用来得到根节点的 getters 信息,这里就不演示了,感兴趣本身能够去尝试。惟一要强调的就是千万不要弄错参数的位置了。

固然,action 中也能接收到 rootGetters,可是在 action 中,因为它接收过来的数据都被包在 context 对象中的,因此解包出来没有什么顺序的限制。

5、命名空间

前面咱们已经知道了,模块内部的 action、mutation 和 getter 默认是注册在全局命名空间的。若是咱们只想让他们在当前的模块中生效,应该怎么办呢?

经过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

咱们在 moduleA.js 中添加 namespaced: true

export default {
    namespaced: true,
    // ...
}
复制代码

这个时候再去运行代码,你会发现以下错误:

[vuex] unknown getter: detail

在全局 getter 中已经找不到 detail 的这个方法了,由于它的路劲已经改变了,再也不属于全局,仅仅只属于 moduleA 了。因此,这个时候,若是咱们想要访问它,必须带上路劲才行。修改 App.vue 以下:

<script>
    import {mapActions, mapGetters} from 'vuex';
    export default {
        computed: {
            ...mapGetters({
                name: 'moduleA/detail'
            }),
        },
        methods: {
            ...mapActions({
                call: 'moduleA/callAction'
            }),
            modifyNameAction() {
                this.call();
            }
        },
    }
</script>
复制代码

注意,若是一个模块启用了命名空间,那么它里面的 getter 和 action 中收到的 getter,dispatch 和 commit 也都是局部化的,不须要在同一模块内额外添加空间名前缀。也就是说,更改 namespaced 属性后不须要修改模块内的任何代码。

那么咱们如何在带命名空间的模块内访问全局内容呢?

经过前面的学习,咱们已经了解到:

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

如今若是想要在全局命名空间内分发 action 或提交 mutation 的话,那么咱们只须要将 将 { root: true } 做为第三参数传给 dispatch 或 commit 便可。

export default {
    namespaced: true,
    // ...
    actions: {
        callAction({state, commit, rootState}) {
            commit('setName', '改变', {root: true});
            alert(state.text + '-' + rootState.name);
        }
    }
}
复制代码

接下来看看如何在带命名空间的模块内注册全局 action

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

写法稍微有点变化,咱们来看看,修改 moduleA.js,以下:

export default {
    namespaced: true,
    // ...
    actions: {
        callAction: {
            root: true,
            handler (namespacedContext, payload) {
                let {state, commit} = namespacedContext;
                commit('setText');
                alert(state.text);
            }
        }
    }
}
复制代码

简单解释下,这里的 namespacedContext 就至关于当前模块的上下文对象,payload 是调用的时候所传入的参数,固然也叫载荷。

示例就讲到这里,接下来看看带命名空间的绑定函数

关于 mapState, mapGetters, mapActionsmapMutations 这些函数如何来绑定带命名空间的模块,上面示例代码中其实已经都写过了,这里再看看另外几种更简便的写法,先看看以前的写法。

这里就用官方的示例代码举例说明:

computed: {
    ...mapState({
        a: state => state.some.nested.module.a,
        b: state => state.some.nested.module.b
    })
},
methods: {
    ...mapActions([
        // -> this['some/nested/module/foo']()
        'some/nested/module/foo', 
        // -> this['some/nested/module/bar']()
        'some/nested/module/bar' 
    ])
}
复制代码

更优雅的写法:

computed: {
    ...mapState('some/nested/module', {
        a: state => state.a,
        b: state => state.b
    })
},
methods: {
    ...mapActions('some/nested/module', [
        'foo', // -> this.foo()
        'bar' // -> this.bar()
    ])
}
复制代码

将模块的空间名称字符串做为第一个参数传递给上述函数,这样全部绑定都会自动将该模块做为上下文。

咱们还能够经过使用 createNamespacedHelpers 建立基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

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

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

6、模块的动态注册

这一章节,官网讲得比较清楚,因此直接搬过来了。

在 store 建立以后,可使用 store.registerModule 方法动态的注册模块:

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

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

模块动态注册功能使得其余 Vue 插件能够经过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如, vuex-router-sync 插件就是经过动态注册模块将 vue-router 和 vuex 结合在一块儿,实现应用的路由状态管理。

你也可使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即建立 store 时声明的模块)。

在注册一个新 module 时,你颇有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你能够经过 preserveState 选项将其归档: store.registerModule('a', module, { preserveState: true })

7、模块重用

就一点,重用会致使模块中的数据 state 被污染,因此和 Vue 中的 data 同样,也使用一个函数来申明 state 便可。

const MyReusableModule = {
 state () {
   return {
     foo: 'bar'
   }
 },
 //...
}
复制代码

写在最后

演示代码写的没啥逻辑,还请见谅,主要仍是为了帮助你们更好的理解 Module 的用法,若是有不理解的地方,欢迎留言告知。

那么到这里,Vuex 的核心概念就已经所有讲完了。不知道你们掌握的如何,虽然这套框架有点抽象,但其实只要咱们真的用心去学了,要弄懂它也是很容易的。不过光看懂仍是不够,必定要在项目中多运用,多实操才可以掌握的更加牢固。

转载说明:

做者:大宏说 连接:www.jianshu.com/p/83d5677b0…

后记

以上就是胡哥今天给你们分享的内容,喜欢的小伙伴记得点赞收藏呀,关注胡哥有话说,学习前端不迷路,欢迎多多留言交流...

胡哥有话说,一个有技术,有情怀的胡哥!现任京东前端攻城狮一枚。 胡哥有话说,专一于大前端技术领域,分享前端系统架构,框架实现原理,最新最高效的技术实践!

相关文章
相关标签/搜索