vuex-types
v1.0.1
Published
基于 Vuex 的多模块状态管理插件
Downloads
2
Readme
VuexTypes 使用文档
VuexTypes 是什么?
VuexTypes
是一个基于 Vuex 二次开发的 Vue 插件。它的主要作用是对 Vuex
中带命名空间的模块的绑定辅助函数(mapState
、mapGetters
、mapMutations
、mapActions
)进行改造,简化 多模块场景下
原生绑定辅助函数的写法。VuexTypes
适用于多模块场景下的状态管理。
我们先来看一下 Vuex
官方提供的关于 带名命空间的绑定函数
的代码片段:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['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'
])
}
}
这两种方式在单模块场景下有了一定的简化,但是如果一个组件使用到了多个模块下的状态管理,那就会有繁琐的重复代码:
// 方式一:
computed: {
...mapState('some/nested/moduleA', {
a: state => state.a,
b: state => state.b
}),
...mapState('some/nested/moduleB', {
a: state => state.a,
b: state => state.b
})
}
// 方式二:
import { createNamespacedHelpers } from 'vuex'
const { mapState: mapStateA, mapActions: mapActionsA } = createNamespacedHelpers('some/nested/moduleA')
const { mapState: mapStateB, mapActions: mapActionsB } = createNamespacedHelpers('some/nested/moduleB')
export default {
computed: {
...mapStateA({
a: state => state.a,
b: state => state.b
}),
...mapStateB({
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActionsA(['foo', 'bar']),
...mapActionsB(['foo', 'bar']),
}
}
VuexTypes
主要就是为了简化多模块场景下的状态管理写法,不需要引入 辅助函数
或者 createNamespacedHelpers
,直接在组件中定义模块状态就可以:
export default {
_mappedState: {
moduleA: {
a: state => state.a,
b: state => state.b
},
moduleB: {
a: state => state.a,
b: state => state.b
}
},
_mappedActions: {
moduleA: () => ['foo', 'bar'],
moduleB: () => ['foo', 'bar']
}
}
此外,在日常开发中,对接口加载数据的处理也是很常见的:为接口数据创建 state 状态,然后定义相应的 mutation 去触发状态变更,同时还会处理接口加载的状态(加载中、加载成功、加载失败)。这些操作 VuexTypes
统统帮你处理了,只需要进行一些配置。话不多说,我们来看下如何使用 VuexTypes
吧。
开始
VuexTypes
接收一些自定义的模块配置,经过插件的处理,最终转换成 Vuex
创建 store 应用的标准格式参数。
下面我们来创建一个简单的 store。创建过程直截了当——仅需要提供一个 root
根模块配置:
import Vue from 'vue';
// 目前还未发布至 npm 仓库,可以先引入本地文件
import * as VuexTypes from 'vuex-types';
Vue.use(VuexTypes);
const store = VuexTypes.createStore({
root: {
mapDefinitionToModule() {
return {
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
}
}
}
});
export default store;
注:在创建应用的时候,配置参数中根模块必须要以
root
为键名,其他模块则需要开启名命空间 (namespace: 'moduleName')
现在,你可以通过 store.state
来获取状态对象,以及通过 store.commit
方法触发状态变更:
store.commit('increment');
console.log(store.state.count) // -> 1
创建好的 store 后,需要将其挂载到 Vue 组件实例上:
new Vue({
el: '#app',
store
});
现在我们可以从组件的方法提交一个变更:
methods: {
increment() {
this.$store.commit('increment');
console.log(this.$store.state.count)
}
}
接下来,我们将会对 Vuex 官方的购物车示例进行改造(最终请查看 购物车示例 ),结合示例代码更深入地探讨 VuexTypes
的核心概念。
核心概念
一、mapTypesToModule - 常量配置
ES2015 允许使用一个常量作为函数名,而 mapTypesToModule
的作用就是定义常量,它可以是数组或者函数形式:
数组形式
模块配置接收一个字符串数组:
// ...
mapTypesToModule: ['GET_ALL_PRODUCTS', 'DECREMENT_PRODUCT_INVENTORY']
函数形式
模块配置也允许接受一个函数,并且其返回值是字符串数组:
// ...
mapTypesToModule: function() {
return ['GET_ALL_PRODUCTS', 'DECREMENT_PRODUCT_INVENTORY']
}
常量配置的使用
最终定义的常量会转化成键名和键值相同的 常量对象
- types
:
types: {
'GET_ALL_PRODUCTS': 'GET_ALL_PRODUCTS',
'DECREMENT_PRODUCT_INVENTORY': 'DECREMENT_PRODUCT_INVENTORY'
}
types
对象会作为参数传递给 模块配置
和 模块辅助函数定义
,后面的章节会介绍到。
二、mapTargetsToModule - 接口数据目标配置
日常业务中,对接口请求的处理通常包括 接口数据处理
、加载状态处理
以及 错误信息处理
,相应的就需要在 state 状态树中创建对应的变量,然后定义 mutation 去触发状态变更,而 mapTargetsToModule
就是为了简化这类重复操作。它接收数组或函数两种形式:
数组形式
接口数据目标配置接收一个字符串数组:
// ...
mapTargetsToModule: ['products']
函数形式
接口数据目标配置也允许接受一个函数,并且其返回值是字符串数组:
// ...
mapTargetsToModule: function() {
return ['products']
}
接口数据目标配置赋予的能力
1. 统一接口数据 state
根据 mapTargetsToModule
配置,字符串数组中的每个元素相应的都会自动创建出具有统一结构的 state,其中包含了 接口数据
、加载状态
、错误信息
:
// 模块的状态树
state = {
// ...
productsLoading: false,
products: {
data: [],
pageIndex: 1,
pageSize: 10,
total: 0,
loaded: false
},
productsError: null
}
2. 扩展常量对象
接口数据目标配置会结合 加载中
、加载成功
、加载失败
这三种接口通用状态生成具有固定形式的数组:
[
'SET_PRODUCTS_LOADING',
'SET_PRODUCTS_SUCCESS',
'SET_PRODUCTS_FAILURE'
]
和 mapTypesToModule
常量配置相同,上面这个固定形式的数组也会转化成键名和键值相同的对象,并合并到 types
常量对象中:
types: {
'SET_PRODUCTS_LOADING': 'SET_PRODUCTS_LOADING,
'SET_PRODUCTS_SUCCESS': 'SET_PRODUCTS_SUCCESS,
'SET_PRODUCTS_FAILURE': 'SET_PRODUCTS_FAILURE
}
3. 创建接口数据 mutations
接口数据 state 需要通过 mutations 触发更新,而根据 mapTargetsToModule
配置会自动创建出三种接口通用状态相应的 mutations,其函数名的形式和上述的 types
常量对象的键值一致。我们分别来看下如何触发状态变更。
接口数据加载中:
// 更新 state.productsLoading 值
commit(types.SET_PRODUCTS_LOADING) // -> productsLoading = true
接口数据加载成功:
我们知道 commit 函数接收的第二个参数即为触发 mutation 的 载荷(payload),而这里我们需要将接口的目标数据(真实的载荷)传递给 payload.data
。
// 更新 state.products 值
// 默认情况 payload.data 会合并到 state.products 对象中
commit(types.SET_PRODUCTS_SUCCESS, {
data: {
data: ['foo', 'bar'],
total: 2
}
})
// 最终的状态值
state = {
// ...
productsLoading: false,
products: [
data: ['foo', 'bar'],
pageIndex: 1,
pageSize: 10,
total: 2,
loaded: false
],
productsError: null
}
除了传递给 payload.data
对象之外,还可以通过 payload.selector
函数来自定义数据传递:
// 通过 payload.selector 的形式提交变更,最终结果和上述写法相同
commit(types.SET_PRODUCTS_SUCCESS, {
selector: function(payload, state) {
return {
data: ['foo', 'bar'],
total: 2
}
}
})
接口数据加载失败:
commit(types.SET_PRODUCTS_FAILURE, {
error: '接口报错信息xxx'
})
// 最终的状态值
state = {
// ...
productsLoading: false,
productsError: '接口报错信息xxx'
}
通过 payload.error
可以传递接口报错信息,同样的也可以采用 payload.selector
的形式:
commit(types.SET_PRODUCTS_FAILURE, {
selector: function(payload, state) {
return '接口报错信息xxx'
}
})
三、mapDefinitionToModule - 模块配置
mapDefinitionToModule
是最核心的配置,它对应的其实就是 Vuex 中五个核心概念(State
、Getter
、Mutation
、Action
、Module
)的配置。它接收对象或函数两种形式:
对象形式
当模块配置以对象形式定义时,它与 Vuex 的核心配置并无差异:
// ...
mapDefinitionToModule: {
state: {},
getters: {},
mutations: {},
actions: {},
modules: []
}
函数形式
模块配置也可以采用函数形式,此时它接收一个对象作为参数,这个对象包含了 types 常量对象
和 命名空间名称
,所以在定义 Vuex 核心配置的时候可以访问这两个变量:
// ...
namespace: 'products'
mapDefinitionToModule({ types, namespace }) {
state: {},
getters: {},
mutations: {
[types.DECREMENT_PRODUCT_INVENTORY](state, payload) {
// ...
}
},
actions: {
[types.GET_ALL_PRODUCTS]({ commit, state }, payload) {
// ...
}
},
modules: []
}
在组件中使用绑定了命名空间的辅助函数
Vuex 官方提供了四个辅助函数,分别是 mapState
、mapGetters
、mapMutations
、mapActions
,当使用这些函数来绑定带命名空间的模块时,写起来可能比较繁琐,我们对其进行了改造,简化了写法。
改造后的辅助函数名称分别为 _mappedState
、_mappedGetters
、_mappedMutations
和 _mappedActions
。它们都是对象形式,以 模块路径
作为键名,便可以访问指定模块下的应用。我们来看下改造后的辅助函数具体是如何使用的:
- _mappedState:
export default {
// ...
_mappedState: {
// 模块 A 命名空间下的状态值
moduleA: {
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
},
// 模块 A 中嵌套的模块 C 命名空间下的状态值
'moduleA/moduleC': ['some_nested_module_state'] // -> 映射名称和 state 子节点名称相同时,可以传递字符串数组
}
}
- _mappedGetters
export default {
// ...
_mappedGetters: {
// 模块 A 命名空间下的计算属性
moduleA: {
// 把 `this.doubleCountAlias` 映射为模块 A 下的 `doubleCount`
doubleCountAlias: 'doubleCount'
}
// 模块 B 命名空间下的计算属性
moduleB: ['doneTodosCount'] // -> 映射名称和 getters 子节点名称相同时,可以传递字符串数组
}
}
- _mappedMutations
export default {
// ...
_mappedMutations: {
// 模块 A 命名空间下的 mutations
moduleA({ types }) {
return {
// 把 `this.add` 映射为模块 A 下的 `INCREMENT`
add: types.INCREMENT
}
},
// 模块 B 命名空间下的 mutations
moduleB({ types }) {
return [types.ADD_TODOS] // -> 映射名称和 mutations 子节点名称相同时,可以传递字符串数组
}
}
}
模块定义除了提供 types
局部常量对象之外,还通过 rootTypes
、rootCommit
、rootDispatch
暴露出根模块的 常量对象
、commit
和 dispatch
。
export default {
// ...
_mappedMutations: {
moduleA({ types, rootTypes, rootCommit, rootDispatch }) {
return {
add(commit, payload) {
// 根模块下的 ROOT_INCREMENT
rootCommit(rootTypes.ROOT_INCREMENT)
// 模块 A 下的 INCREMENT
commit(types.INCREMENT)
}
}
}
}
}
- _mappedActions
同 _mappedMutations 一样,也提供了 types
、rootTypes
、rootCommit
、rootDispatch
四个参数,但是更常用的写法是直接定义根模块,在其他模块直接使用根模块的方法即可:
export default {
// ...
_mappedActions: {
root({ types }) {
return {
incrementIfOddOnRootSum: types.INCREMENT_IF_ODD
}
},
// 模块 A 命名空间下的 actions
moduleA({ types, rootTypes, rootCommit, rootDispatch }) {
return {
// 把 `this.getList` 映射为模块 A 下的 `GET_LIST`
getList: types.GET_LIST
}
}
}
}
自定义辅助函数名称
在初始化插件时,可以通过配置一个前缀 mapPrefix
来自定义辅助函数名称:
Vue.use(VueTypes, {
mapPrefix: '_myCustom'
}) // -> 改造后的辅助函数名称:_myCustomState、_myCustomGetters、_myCustomMutations、_myCustomActions
四、mapMergeStrategy - 接口数据目标更新策略
在 接口数据目标配置
这一章节中我们知道了这一配置赋予了三种能力,其中一个是 创建接口数据 mutations
,在接口数据加载成功时,会将 真实载荷
更新到 state 状态树中,默认的更新策略采用 merge
合并的方式,即将新状态合并到旧状态中。
根据 mapMergeStrategy
配置,我们可以自定义接口数据加载成功时的更新策略。mapMergeStrategy
配置接收一个对象形式,键名需要和 接口数据目标配置
对应,键值为函数形式,接收的第一个参数表示旧状态,第二个参数表示新状态:
// ...
mapTargetsToModule: ['products'],
mapMergeStrategy: {
// 处理 products 接口数据模块的策略
products: function(prevState, nextState) {
// 将新状态直接覆盖掉旧状态
return nextState
}
}
接口数据目标 products 具有的初始 state 状态如下:
products: {
data: [],
pageIndex: 1,
pageSize: 10,
total: 0,
loaded: false
}
我们来触发 products 状态变更:
commit(types.SET_PRODUCTS_SUCCESS, {
data: {
data: ['foo', 'bar'],
total: 2
}
})
根据上面的例子,我们定义的更新策略是采用覆盖的方式,最终更新之后 products 状态如下:
// 覆盖了旧的 products 状态值
products: {
data: ['foo', 'bar'],
total: 2
}