Vuex为Vue中提供了集中式存储 + 库,其主要分为state、getter、mutation、action四个模块,它们每个担任了不同角色,分工不同;Vuex允许所有的组件共享状态抽取出来,以一个全局单例模式管理,状态集中存储在同一个地方,并且以相同的方式访问和修改。
这样使得组件间的通信变的简单,组件之间不需要直接通信,只需要从Vuex的store中获取它们需要的数据即可。
虽然Vuex在Vue中担任重要的角色,通过可预测化的状态管理模式来帮助开发者更好地管理复杂应用的状态。但是当遇到某些模块业务属性较多情况下,定义和管理也是相当繁琐,尤其是对其命名和调用,所以此时则需要通过有效地简化程序代码,来减少不必要的冗余,提升代码的整体质量。
一、Vuex的使用
在Vue2中,关于vuex的安装和使用已写过一篇,有不了解可以前去查看,地址:Vue.js快速入门之二:使用状态管理工具Vuex_控制台输出vuex的数据-CSDN博客
在这里,我们使用在store/index.js中定义userInfo和accessToken状态,代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
// 定义属性
state: {
userInfo: '',
accessToken: ''
},
// 定义计算属性
getters: {
user_info(state){
return state.userInfo
},
access_token(state){
return state.accessToken
}
},
// 定义业务处理函数
actions: {
userInfoChange({commit}, value) {
commit('USER_INFO_CHANGE', value)
},
accessTokenChange({commit}, value) {
commit('ACCESS_TOKEN_CHANGE', value)
}
},
// 定义属性裂变方法
mutations: {
USER_INFO_CHANGE(state, value) {
state.userInfo = value
},
ACCESS_TOKEN_CHANGE(state, value){
state.accessToken = value
}
}
});
此时Vuex中则会出现这两个属性了,如下图:
二、较复杂业务状态管理
对于比较常规的、属性较少的,且全局统一使用的可以定义在根节点,但对于某些特定业务或属性较多的,则需要单独定义一个子状态树去管理。
在store目录中创建modules文件夹,用于创建和定义子模块业务的状态管理器。
例如,商城App中,除了商品列表可以下单外,首页中精品推荐、热销商品等等商品展示位也可以点击下单。这样支付需要的信息,可以统一放到一个特定业务状态管理器中,做到集中管理。
首先,创建modules/order.js用于处理订单业务模块。代码如下:
/**
* 定义订单业务模块
*/
export default {
state: {
goodsName: '', // 商品名称
goodsPrice: 0, // 商品价格
goodsNumber: 0, // 商品选择数量
couponList: [], // 使用 优惠券 列表
freight: 0, // 运费
originPrice: 0, // 原价格
},
getters: {
goods_name(state) {
return state.goodsName
},
goods_price(state) {
return state.goodsPrice
},
goods_number(state) {
return state.goodsNumber
},
coupon_list(state) {
return state.couponList
},
// 优惠券价格之和
coupon_use_price(state){
return state.couponList.reduce((total, item) => total + item.price, 0)
},
coupon_number(state) {
return state.couponList.length
},
freight_price(state) {
return state.freight
},
origin_price(state) {
return state.originPrice
},
// 计算支付价格
pay_price(state) {
return state.goodsPrice * state.goodsNumber + state.freight
},
// 计算应付金额
total_price(state, getter) {
return getter.pay_price - getter.coupon_use_price
},
// 节约成本
cost_saving(state, getter) {
return (state.originPrice * state.goodsNumber - getter.total_price).toFixed(1)
}
},
actions: {
// 更新属性参数
orderInfoChange({commit}, params) {
if('undefined' !== typeof params['goodsName']) commit('GOODS_NAME', params.goodsName)
if('number' === typeof params['goodsPrice']) commit('GOODS_PRICE', params.goodsPrice)
if('number' === typeof params['goodsNumber']) commit('GOODS_NUMBER', params.goodsNumber)
if(params['couponList']&&Array.isArray(params['couponList'])) commit('COUPON_LIST', params.couponList)
if('number' === typeof params['freight']) commit('FREIGHT', params.freight)
if('number' === typeof params['originPrice']) commit('ORIGIN_PRICE', params.originPrice)
}
},
mutations: {
GOODS_NAME(state, value) {
state.goodsName = value
},
GOODS_PRICE(state, value) {
state.goodsPrice = value
},
GOODS_NUMBER(state, value) {
state.goodsNumber = value
},
COUPON_LIST(state, value) {
state.couponList = value
},
FREIGHT(state, value) {
state.freight = value
},
ORIGIN_PRICE(state, value) {
state.originPrice = value
}
}
}
此时,vuex中状态树如下图:
此时我们在页面中调用orderInfoChange业务方法,为状态管理器中添加点数据,再看看效果,代码如下:
<script>
export default {
name: 'App',
data(){
return {}
},
created() {
this.$store.dispatch('orderInfoChange', {
goodsName: '红薯粉丝500g', // 商品名称
goodsPrice: 29.0, // 商品价格
goodsNumber: 10, // 商品选择数量
couponList: [
{name: "券1", price: 0.5},
{name: "券2", price: 1.2}
],
freight: 20, // 运费
originPrice: 32.5, // 原价格
})
}
}
</script>
如下图,只管将订单原始数据传入,通过Vuex对数据统一管理和集中业务处理,由Vuex完成数据计算、修改和输出,而不必在每个业务中单独计算,从而保证结果的一致性。
在下单页面,直接取getters中的计算属性即可,当state中属性值改造后,其值也会实时更新为最新数据和数据合成结果。
三、精简模块内属性定义
如上下单业务,是方便了各业务模块中,商品下单信息的统一管理和业务数据处理;但是对于四大模块中定义如此多属性和方法,随着项目中功能升级和信息增加,定义属性和方法越来越多,对后期管理和维护的工作量也是比较大的。
对于此处,个人对Vuex有一些见解,通过程序自动完成部分属性和方法的定义,来简化流程。状态管理中,getters和actions的计算属性和业务层较多,而state和mutations则比较单一,所以这里将利用state,通过函数方法自动完成相关属性和函数的定义,再将其结果还原到状态树中。
第一步:在utils/utils.js文件中定义useStateAndMutations()函数,用于重构状态树中需要的属性和方法。代码如下:
/**
* 驼峰转下划线
*/
const camelCaseToSnakeCase = str => {
return str.replace(/\B(?=[A-Z])/g, '_')
}
/**
* 构建state与mutations,以commit执行类型常量
*/
export const useStateAndMutations = (CONST_PARAMS) => {
// 重构数据
const list = Object.keys(CONST_PARAMS).map(item => {
return {
origin: item, // 原始值
upper: camelCaseToSnakeCase(item).toUpperCase(), // 下划线 - 大写
snake: camelCaseToSnakeCase(item).toLowerCase() // 下划线 - 小写
}
})
// mutationTypes 常量定义
const mutationTypes = list.reduce(
(obj, item) => Object.assign(obj, { [item.upper]: item.origin }),
{}
);
// getters 计算属性定义(如果直接使用state数据,则此部分可以忽略)
const getters = list.reduce(
(obj, item) => Object.assign(obj, { [item.snake](state){ return state[item.origin] } }),
{}
)
// mutations 定义
const mutations = list.reduce(
(obj, item) => Object.assign(obj, { [item.origin](state, value){ state[item.origin] = value } }),
{}
)
// 对外暴露 生产出来的数据
return { mutationTypes, getters, mutations }
}
第二步:将@/utils/utils.js引入到order.js文件中,然后通过useStateAndMutations()解构出mutationTypes, getters, mutations,并将getters和mutations还原到状态树中。代码如下:
import { useStateAndMutations } from '@/utils/utils'
// 定义state属性
const useState = {
goodsName: '', // 商品名称
goodsPrice: 0, // 商品价格
goodsNumber: 0, // 商品选择数量
couponList: [], // 使用 优惠券 列表
freightPrice: 0, // 运费
originPrice: 0, // 原价格
}
// 执行构建函数
const { mutationTypes, getters, mutations } = useStateAndMutations(useState)
/**
* 定义订单业务模块
*/
export default {
state: useState,
getters: {
...getters,
// 优惠券价格之和
coupon_use_price(state){
return state[mutationTypes.COUPON_LIST].reduce((total, item) => total + item.price, 0)
},
coupon_number(state) {
return state[mutationTypes.COUPON_LIST].length
},
// 计算支付价格
pay_price(state) {
return state[mutationTypes.GOODS_PRICE] * state[mutationTypes.GOODS_NUMBER] + state[mutationTypes.FREIGHT_PRICE]
},
// 计算应付金额
total_price(state, getter) {
return getter.pay_price - getter.coupon_use_price
},
// 节约成本
cost_saving(state, getter) {
return (state[mutationTypes.ORIGIN_PRICE] * state[mutationTypes.GOODS_NUMBER] - getter.total_price).toFixed(1)
}
},
actions: {
// 更新属性参数
orderInfoChange({commit}, params) {
if('undefined' !== typeof params[mutationTypes.GOODS_NAME])
commit(mutationTypes.GOODS_NAME, params[mutationTypes.GOODS_NAME])
if('number' === typeof params[mutationTypes.GOODS_PRICE])
commit(mutationTypes.GOODS_PRICE, params[mutationTypes.GOODS_PRICE])
if('number' === typeof params[mutationTypes.GOODS_NUMBER])
commit(mutationTypes.GOODS_NUMBER, params[mutationTypes.GOODS_NUMBER])
if(params[mutationTypes.COUPON_LIST]&&Array.isArray(params[mutationTypes.COUPON_LIST]))
commit(mutationTypes.COUPON_LIST, params[mutationTypes.COUPON_LIST])
if('number' === typeof params[mutationTypes.FREIGHT_PRICE])
commit(mutationTypes.FREIGHT_PRICE, params[mutationTypes.FREIGHT_PRICE])
if('number' === typeof params[mutationTypes.ORIGIN_PRICE])
commit(mutationTypes.ORIGIN_PRICE, params[mutationTypes.ORIGIN_PRICE])
}
},
mutations
}
此时再看order.js文件中代码,已经明显减少了很多代码,最终Vuex状态管理器中属性结构还是和之前一样,如下图:
输出mutationTypes, getters, mutations三个结果,大家可能会比较好理解useStateAndMutations()方法到底做了什么,如下图:
通过useStateAndMutations()方法,只需两步操作,就将本该手动定义的常量、计算属性和修改属性值(裂变)方法,用state属性中的key值来完成,从而简化操作。
四、orderInfoChange优化
在orderInfoChange方法中,大家可能看的眼花缭乱,但可以看出都是通过mutationTypes常量值来取值的,所以通过以下代码,对代码进行分类处理和优化,从而简化并能更好理解。
actions: {
// 更新属性参数
orderInfoChange({commit}, params) {
if('undefined' === typeof params) return;
// 字符串数据处理
[mutationTypes['GOODS_NAME']].forEach(key => {
if('string' === typeof params[key]) commit(key, params[key])
});
// number数据处理
[
mutationTypes.GOODS_PRICE,
mutationTypes.GOODS_NUMBER,
mutationTypes.FREIGHT_PRICE,
mutationTypes.ORIGIN_PRICE
].forEach(key => {
if('number' === typeof params[key]) commit(key, params[key])
});
// 数组数据处理
[mutationTypes.COUPON_LIST].forEach(key => {
if(params[key]&&Array.isArray(params[key])) commit(key, params[key])
});
}
}
进行调整后,商品信息传入orderInfoChange中后,数据依然正常赋值及裂变,如下图:
综上所述,我们在JavaScript开发中,可以简化程序代码和减少不必要的冗余代码,来提高代码质量、可维护性和可读性。函数式编程中,利用高阶函数(如map、filter、reduce等数组方法)和纯函数来简化对数组和集合的操作;编写可复用的函数,避免重复代码。