type
status
date
slug
summary
tags
category
icon
password

vuex 辅助工具函数的实践与解析

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
在 vuex 中提供了几个辅助函数来帮助我们减少代码的重复和冗余。
PS: 文章辅助函数介绍部分内容引用 Vuex 文档

vue-help Example 🌰

为了让小伙伴们对项目更加直观,写了个小 Demo Github | vue-helps,也可以访问 Github Page | vue-helps Example 🌰

辅助函数介绍

mapState

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
当一个组件需要获取多个状态时候,我们可以使用 mapState 辅助函数帮助我们生成计算属性:
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。如果当前 computed 中有其他的 computed,可以通过对象展开运算符 ... 与当前 vue 实例的 computed 进行合并。

mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
如果你想将一个 getter 属性另取一个名字,使用对象形式:

mapMutations

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

mapActions

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

createNamespacedHelpers

你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

项目实战

在构建大型应用的时候,我们会将 Vuex 相关代码分割到模块中。下面是项目结构示例:

结合 minxins

我们可以将可以复用的部分提取到 mixin 中,只要引入 mixin ,就能在页面中直接调用。
  • * html :**
在页面调用 checkPermission 方法,如果没有权限,则返回 false,对应的 html 不渲染。
javascript :
在 javascript 中引入 checkPermissionMixin:
checkPermissionMixin :
在 checkPermissionMixin 中调用辅助工具函数,获取权限列表,并申明 checkPermission 检测权限的函数。

源码解析

在 vue 的入口文件 index.js 使用 export default 默认导出了 mapState、mapMutations、 mapGetters、 mapActions、createNamespacedHelpers 辅助工具函数。
我们可以通过解构的方式获得 vuex 暴露的辅助工具函数。
关于辅助工具函数的代码在 src/helpers.js:
可以看到 helpers.js 向外暴露了 5 个辅助工具函数,在 vuex 入口文件中包装成对象后暴露出去。

mapState

mapState 辅助函数帮助我们生成计算属性。
来看一下具体实现:
mapState 函数是经过 normalizeNamespace 函数处理后返回的函数,在调用 normalizeNamespace 的时候传入了回调函数。

normalizeNamespace

我们先来看看 normalizeNamespace 函数:
normalizeNamespace 函数接收一个 fn 回调作为参数,也就是 mapState 传入的回调函数。
此时 mapState 就是这个返回的函数,它接收 namespace 、map 作为参数,namespace 就是命名空间,map 就是传过来的 state。
首先会判断 namespace 是否是一个字符串,因为 mapState 第一个参数是可选的,如果不是字符串就说明没有命名空间,第一个参数就是传入的 state,将 namespace 赋值给 map,然后将 namespace 赋值为空字符串。进入 else if 后判断 namespace 最后一个字符串是否是 /,没有就拼上 /
当调用 mapState 的时候,就会返回 fn(namespace, map) 函数的运行后的结果,就是一个 res 对象。
PS: normalizeNamespace 是一个高阶函数实现,高阶函数是接收一个或者多个函数作为参数,并返回一个新函数的函数。
我们来看一下 mapState 中的 fn 具体实现。
首先申明一个 res 对象,作为循环赋值后返回结果,然后调用 normalizeMap 函数, normalizeMap 接收一个对象或者数组,转化成一个数组形式,数组元素是包含 key 和 value 的对象。

normalizeMap

经过 normalizeMap 函数处理后,会转化成一个数组, [{key: key, val: fn}] 的格式,调用 forEach 循环处。
在 forEach 的回调函数中,使用解构取出 key 和 value,每一次循环就以 key 为键,mappedState 函数为 value 存入 res 对象, 在 mappedState 函数中,声明 state 和 getters 变量保存 this.$store.statethis.$store.getters
接着判断传入的 namespace,如果有 namespace 就调用 getModuleByNamespace 函数搜索对应模块,如果没有搜索到就 return,有对应模块的话将对应模块的 state、getters 赋值给声明的 state 和 getters 变量。
mappedState 最后判断 val 是否是 function,是就调用 call 将 val 的 this 绑定到 Vue 实例,并将 state、 getters 作为参数传递,执行后返回,不是 function 根据 key 返回对应的 state。

getModuleByNamespace

getModuleByNamespace 函数主要用来搜索具有命名空间的模块。
函数开始申明 module 变量,然后根据 namespace 从 store._modulesNamespaceMap 取出对应模块, _modulesNamespaceMap 这个变量是在 Store 类中,调用 installModule 时候保存所以所有命名空间模块的变量。
判断非生产环境并且没有对应模块,抛出异常,最后将 module 变量返回。
forEach 最后还有一段:
应该是 devtools 需要这个属性判断 value 是否属于 vuex。
完成 forEach 循环后会将处理后的 res 对象返回。

mapMutations

mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用。
来看一下具体实现:
mapMutations 处理过程与 mapState 相似,我看来看看传入 normalizeNamespace 的回调函数。
首先也是申明 res 空对象,经过 normalizeMap 函数处理后的 mutations 调用 forEach 循环处理,在 forEach 的回调函数中, 使用解构取出 key 和 value,每一次循环就以 key 为键、mappedMutation 函数为 value 存入 res 对象, 在 mappedMutation 函数中,声明 commit 变量保存 this.$store.commit 。
判断传入的 namespace,如果有 namespace 就调用 getModuleByNamespace 函数搜索对应模块,如果没有搜索到就 return,有对应模块的话对应模块的将 commit 赋值给声明的 commit 变量。
mappedMutation 最后判断 val 是否是 function,是就调用 apply 将 val 的 this 绑定到 Vue 实例,并将 commit 和 args 合并成一个数组作为参数传递,,val 不是 function 就将 commit 调用 apply 改变了 this 指向,将 val 和 args 合并成一个数组作为参数传递,执行后返回。
最后将 res 对象返回。

mapGetters

mapGetters 辅助函数将 store 中的 getter 映射到局部计算属性。
来看一下具体实现:
我看来看看传入 normalizeNamespace 的回调函数。
首先也是申明 res 空对象,经过 normalizeMap 函数处理后的 getters 调用 forEach 循环处理,在 forEach 的回调函数中, 使用解构取出 key 和 value,每一次循环就以 key 为键、mappedGetter 函数为 value 存入 res 对象,这里会将 val 赋值成 namespace + val,如果有命名空间,此时的 val 应该是类似这样的: cart/cartProducts
在 mappedGetter 函数中,首先判断如果有 namespace 并且调用 getModuleByNamespace 函数没有匹配到对应模块就直接 return。
然后判断在非生产环境并且 this.$store.getters 没有对应的 val 就抛出异常并返回。接下来就是有对应模块的情况,直接返回 this.$store.getters 对应的 getter。
最后将 res 对象返回。

mapActions

mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用。
来看一下具体实现:
mapActions 处理过程与 mapMutations 函数一模一样,就不在赘述。

createNamespacedHelpers

createNamespacedHelpers 创建基于某个命名空间辅助函数。
来看一下具体实现:
createNamespacedHelpers 函数接受一个字符串作为参数,返回一个包含 mapState 、mapGetters 、mapActions 和 mapMutations 的对象。
以 mapState 为例,调用 mapState 函数的 bind 方法,将 null 作为第一个参数传入,不会改变 this 指向,namespace 作为第二个参数。
此时的 mapState 函数就是经过 bind 处理过的,会将 namespace 作为第一个参数传入。
相当于下面这样:
简化了重复写入命名空间。
到此 helpers.js 结束。

一些小问题

这是一个小伙伴问我的,下面是我的理解:

mapState 和 mapGetters 有什么不同,各自适用什么场景 ?

我们先来明确一下概念:
  • mapGetters 辅助函数将 store 中的 getter 映射到 computed;
  • mapState 辅助函数将 store 中对应的属性映射到 computed;
归根到底,其实是 state 和 getters 的区别:state 是一个以 key、value 的形式储存状态的对象,而 getters 则是 key: fn 形式的对象,fn 就类似 state => state.userInfo
当我们在组件或者页面中获取 userInfo,我们可以通过 mapGetters:
也可以通过 mapState:
相比较来说 mapGetters 的代码更为简洁。
🤔
总结一下
mapGetters 其实是将重复的取值行为抽象到 mapGetters 中,而我们能够通过更少的代码得到相同的作用,但是如果这个取值只是出现一次,没有必要通过 mapGetters,我们就可以用 mapState 。

在项目中,一些与后端交互的代码,一般是放在 action 里面好些,还是放在 vue 文件处理好呢 ?

这个一般来说,我会把业务代码放到 vue 文件中,由于业务的特殊和多变性,代码往往是不可复用的。但是如果有一业务代码可以复用,你也可以尝试放到 action 中,记得确保复用的代码不会改动。
但是有一些关于用户信息、权限以及项目中有很多页面会用到的数据,我会通过 mapAction 去获取,然后需要获取对象数据的时候,通过 mapGetters 获取。
vuex-devtoolVuex 是什么?
张小手
张小手
一个普通的干饭人🍚