npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vuex-types

v1.0.1

Published

基于 Vuex 的多模块状态管理插件

Downloads

2

Readme

VuexTypes 使用文档

VuexTypes 是什么?

VuexTypes 是一个基于 Vuex 二次开发的 Vue 插件。它的主要作用是对 Vuex 中带命名空间的模块的绑定辅助函数(mapStatemapGettersmapMutationsmapActions)进行改造,简化 多模块场景下 原生绑定辅助函数的写法。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']()
    ])
}

为了简化上述写法,官方提供了两种形式来绑定命名空间:

  1. 通过将模块的空间名称字符串作为第一个参数传递给辅助函数,会自动绑定到对应命名空间
computed: {
    ...mapState('some/nested/module', {
        a: state => state.a,
        b: state => state.b
    })
},
methods: {
    ...mapActions('some/nested/module', [
        'foo', // -> this.foo()
        'bar' // -> this.bar()
    ])
}
  1. 通过 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 中五个核心概念(StateGetterMutationActionModule)的配置。它接收对象或函数两种形式:

对象形式

当模块配置以对象形式定义时,它与 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 官方提供了四个辅助函数,分别是 mapStatemapGettersmapMutationsmapActions,当使用这些函数来绑定带命名空间的模块时,写起来可能比较繁琐,我们对其进行了改造,简化了写法。

改造后的辅助函数名称分别为 _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 局部常量对象之外,还通过 rootTypesrootCommitrootDispatch 暴露出根模块的 常量对象commitdispatch

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 一样,也提供了 typesrootTypesrootCommitrootDispatch 四个参数,但是更常用的写法是直接定义根模块,在其他模块直接使用根模块的方法即可:

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
}