背景
有一天,产品大佬找到我说业务变更,需要增加一种订单。我心想:“小意思,不就是增加一种订单嘛,洒洒水啦”。介于需求简单,我就把刚要掏出来的收款码又放了回去。产品大佬满意的点点头,带着他的祖传菜刀走了。
于是我开开心心的打开编辑器,一看代码,好家伙,我直接好家伙
computed: {
priceTitleText() {
if ([20001, 20002].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
},
复制代码
想加订单类型时郁闷了,这怕不是每种状态都得去判断订单类型,也就是像下面这样
if (this.detail.orderType === 10001) {
if ([20001, 20002].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
} else if (this.detail.orderType === 10002) { // 新需求订单类型
// 因为订单状态不同展示的内容也不同
if ([20001, 20002, 20003].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
}
复制代码
这么一个computed
还好,但是项目中是有十多二十来个的监听函数,以至于我不得不每个都得去加一下订单类型判断。我思索了一会儿,又掏出了我的收款码,草率了......
使用状态机
就拿上面的需求来讲,当前新需求增加了一种订单类型,假设后面还要增加,是不是还得在上面的操作再去增加一层if else
。每种订单类型有不同的业务逻辑,很有可能状态1在订单类型1上是一种表现形式,但是在订单类型2上又是另外一种表现形式了,所以不得不想想怎么去整理分类订单,让订单逻辑一眼清晰可见。这时,状态机也就派上了用场。
状态机的四大概念
-
State (状态)。一个状态机至少要包含两个状态。例如上面订单类型,有
订单类型1
和订单类型2
两个状态。 -
Event (事件)。事件就是执行某个操作的触发条件或者口令。
-
Action (动作)。事件发生以后要执行动作。对于上面的
priceTitleText
,就是一个动作。 -
Transition (变换)。也就是从一个状态变化为另一个状态。
创建状态机
class StatusMaker {
constructor(detail) {
if (!detail) return
this.state = this.setState(detail.orderType)
this.detail = detail
}
}
复制代码
传入订单详情数据
增加状态(state)
class StatusMaker {
constructor(detail) {
if (!detail) return
this.state = this.setState(detail.orderType)
this.detail = detail
}
// ++ start
setState(status) {
if (status === 2) {
return 'orderType1'
} else if (status === 3) {
return 'orderType2'
}
}
// ++ end
}
复制代码
状态是控制状态机运行的关键属性,所以首先得控制传入的订单类型
增加动作(action)
知道了当前订单类型,那么可以根据当前类型执行需要的不同的方法。
class StatusMaker {
constructor(detail) {
if (!detail) return
this.state = this.setState(detail.orderType)
this.detail = detail
}
setState(status: OrderStatus) {
if (status === 2) {
return 'orderType1'
} else if (status === 3) {
return 'orderType2'
}
}
// ++ start
orderType1 = {
// that: this,
// 上方的that这里使用,在vue中有个bug,当通过this访问实例环境时
// vue内部依赖收集会不停的循环进行addDep,造成爆栈,
// 在浏览器上并不会,有点无语,所以造成了下面的方法内部无法获得当前实例。
// 下方的this我们后续通过call更改this指向。
priceTitleText() {
// console.log(this.that.detail) 本可以这么访问实例
if ([20001, 20002].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
},
}
orderType2 = {
// that: this,
priceTitleText() {
if ([20001, 20002, 20003].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
},
}
// ++ end
}
复制代码
增加执行事件(event)
简单来讲,就是去执行我们需要执行的动作
class StatusMaker {
constructor(detail) {
if (!detail) return
this.state = this.setState(detail.orderType)
this.detail = detail
}
setState(status: OrderStatus) {
if (status === 2) {
return 'orderType1'
} else if (status === 3) {
return 'orderType2'
}
}
orderType1 = {
// that: this, // 这里在vue中有个bug,当通过this访问实例环境时,会造成爆栈,在浏览器上并不会,有点无语,所以造成了下面的方法内部无法获得当前实例。下方的this我们后续通过call更改this指向。
priceTitleText() {
if ([20001, 20002].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
},
}
orderType2 = {
// that: this,
priceTitleText() {
if ([20001, 20002, 20003].includes(this.detail.orderStatus)) {
return '应付金额'
} else {
return '实付金额'
}
}
}
// ++ start
event (method, ...args) {
const state = this.state
if (!this[state] || !this[state][method]) {
return false
}
return this[state][method].call(this, ...args) // 通过call让action内部能够获取当前实例this,并且将参数args传入
}
// ++ end
}
export default StatusMaker
复制代码
如此,一个状态机便大功告成。
vue中使用
import StatusMaker from './statusFactory'
export default {
data() {
return {
statusMaker: null
}
},
computed: {
priceTitleText() {
return this.statusMaker.event('priceTitleText', this.showFormType)
},
// ...
},
created() {
this.statusMaker = new StatusMaker(this.detail)
}
}
复制代码
如此,状态机便可以根据我们传入的订单类型,展示不同的页面逻辑,非常的银杏。
总结
状态机其实也就是状态模式,好处是让整个逻辑更加清晰分明,将不同订单类型数据划片拆分开来。但是个人觉得在使用中,两种订单类型,需要去重复写同样的函数,并且里面大部分内容都相同。不知道有没有其它方式进行优化。欢迎大佬们评论指出,万分感谢。