亲宝软件园·资讯

展开

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

胡哥有话说 人气:0
### 写在前面 这一讲是 Vuex 基础篇的最后一讲,也是最为复杂的一讲。如果按照官方来的话,对于新手可能有点难以接受,所以想了下,决定干脆多花点时间,用一个简单的例子来讲解,顺便也复习一下之前的知识点。 首先还是得先了解下 Module 的背景。我们知道,Vuex 使用的是单一状态树,应用的所有状态会集中到一个对象中。如果项目比较大,那么相应的状态数据肯定就会更多,这样的话,store 对象就会变得相当的臃肿,非常难管理。 这就好比一家公司只有老板一个人来管理一样。如果小公司倒还好,公司要是稍微大一点,那就麻烦了。这个时候,老板就会成立各大部门,并给各大部门安排一个主管,把管理的任务分派下去,然后有什么事情需要处理的话,只需要跟这几个主管沟通,由主管再把任务分配下去就行了,这就大大提高了工作效率,也减轻了老板的负担。 那么同样的道理,Module 其实就承担了部门管理员的角色,而 store 就是老板。理解了这一层,那么后面就好办多了,接下来,咱们就一步一步动起手来开始实践。 一、准备工作 这里我们使用官方提供的 [vue-cli](https://cli.vuejs.org/zh/guide/installation.html) 来建一个项目「vuex-test」。当然,先得安装 vue-cli: ``` npm install -g @vue/cli # OR yarn global add @vue/cli ``` 安装完成后,就可以使用以下命令来创建项目了: ``` vue create vuex-test ``` 还可以使用图形化界面来创建: ``` vue ui ``` 具体关于 vue-cli 的使用方法可以去官方查看,[戳此进入 ](https://cli.vuejs.org/zh/guide/installation.html)。 项目创建完成以后,找到项目安装的目录,并打开控制台执行: ``` // 先定位到项目的目录下 cd vuex-test // 然后安装 vuex npm install vuex --save // 运行一下 npm run serve ``` 运行完成,可以打开 http://localhost:8080/ 看下效果。 最后大家找一个自己比较比较喜欢的 IDE 来打开这个项目,方便查看和编辑。我个人比较喜欢用 WebStore,这里也推荐给大家。 ### 二、简单入手 ![项目的默认结构图](https://img2020.cnblogs.com/other/1103694/202004/1103694-20200428073712152-82262475.png) 这里,我们只看 ```src``` 目录,其他的暂时不管。组件 ```components``` 不在这一讲的范围内,所以也可以忽视,资源 ```assets``` 也没什么可说的,就是如果有图片或者视频什么的话,都放在这个文件夹里面就是了。 我们打开 ```App.vue``` 文件,去掉组件的相关代码,并编写一点简单的 vue 代码。修改如下: ``` ``` 现在我们引入 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``` 中的代码如下: ``` ``` 想必弄懂这些代码,应该都是没啥问题的,因为这些都是 Vuex 很基础的知识点,这里实操来简单回顾一下,加深印象。如果看不懂,那证明之前的基础知识还没掌握。 ### 三、引入 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``` 中,修改如下: ``` ``` 运行然后点击修改,我们会发现模块 A 和 B 中的 ```text``` 值都改变了。当然,action 的用法一模一样,大家也可以试试。 如果模块之间的数据有交集的话,那么我们其实就可以通过这种方式,来同步更新模块之间的数据,虽然看起来非常的方便,但是用的时候可一定要谨慎,这种处理方式一旦没用好,遇到错误,排查起来还是比较有难度的。 ### 四、访问根节点 我们已经知晓,模块内部的 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``` : ``` ``` 然后运行你会发现,根节点的数据已经被我们获取到了。这里需要注意的是在 getters 中,rootState 是以第三个参数暴露出来的,另外,还有第四个参数 rootGetters,用来获得根节点的 getters 信息,这里就不演示了,感兴趣自己可以去尝试。唯一要强调的就是千万不要弄错参数的位置了。 当然,action 中也能接收到 rootGetters,但是在 action 中,由于它接收过来的数据都被包在 ```context``` 对象中的,所以解包出来没有什么顺序的限制。 ### 五、命名空间 前面我们已经知道了,模块内部的 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``` 如下: ``` ``` 注意,如果一个模块启用了命名空间,那么它里面的 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```, ```mapActions``` 和 ```mapMutations``` 这些函数如何来绑定带命名空间的模块,上面示例代码中其实已经都写过了,这里再看看另外几种更简便的写法,先看看之前的写法。 这里就用官方的示例代码举例说明: ``` 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' ]) } } ``` ### 六、模块的动态注册 这一章节,官网讲得比较清楚,所以直接搬过来了。 > 在 store 创建之后,可以使用 ```store.registerModule``` 方法动态的注册模块: ``` // 注册模块 `myModule` store.registerModule('myModule', { // ... }) // 注册嵌套模块 `nested/myModule` store.registerModule(['nested', 'myModule'], { // ... }) ``` > 之后就可以通过 ```store.state.myModule ``` 和 ```store.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 }) ```。 ### 七、模块重用 就一点,重用会导致模块中的数据 state 被污染,所以和 Vue 中的 data 一样,也使用一个函数来申明 state 即可。 ``` const MyReusableModule = { state () { return { foo: 'bar' } }, //... } ``` ### 写在最后 演示代码写的没啥逻辑,还请见谅,主要还是为了帮助大家更好的理解 Module 的用法,如果有不理解的地方,欢迎留言告知。 那么到这里,Vuex 的核心概念就已经全部讲完了。不知道大家掌握的如何,虽然这套框架有点抽象,但其实只要我们真的用心去学了,要弄懂它也是很容易的。不过光看懂还是不够,一定要在项目中多运用,多实操才能够掌握的更加牢固。 转载说明: > 作者:大宏说 链接:https://www.jianshu.com/p/83d5677b0928 ## 后记 以上就是胡哥今天给大家分享的内容,喜欢的小伙伴记得```点赞```、```收藏```呦,关注胡哥有话说,学习前端不迷路,欢迎多多留言交流... > 胡哥有话说,一个有技术,有情怀的胡哥!现任京东前端攻城狮一枚。 > 胡哥有话说,专注于大前端技术领域,分享前端系统架构,框架实现原理,最新最高效的技术实践!

加载全部内容

相关教程
猜你喜欢
用户评论