前言
大家好,我是前端贰货道士。最近这一个月很忙,一直想抽空整理一篇关于如何写出可维护代码的文章,于是这篇文章横空出世。其实使用GPT
可以一键生成很多篇文章,但这样的做法的确违背初心(记录并分享一名前端小白的学习心得
),于是被我毅然决然的舍弃了。愚以为好文需要长篇大论,侃侃而谈,殊不知好文也可以细水长流,源远流长。既然如此,那就返璞归真,回到最初的状态。听君一席话胜似一席话, 本文会持续更新,有需要的小伙伴可以一键收藏, 蟹蟹大家~
1. 善用策略模式思想(取缔if else
的多层嵌套,本质上是switch case
,但更简便)
a. 栗子1
举个栗子:
比如我们封装一个组件,需要同时兼容批量上传效果图
和批量上传刀版图
这两个功能。我们需要依据不同功能,展示不同的title
、选择不同的key
值以及调用不同的接口fun
等。
一般的写法是根据父组件传入的type
,使用三目运算符,定义一个用于判断当前状态的计算属性,然后根据这个计算属性引出其他的计算属性(如title、key、fun
)。但这种写法往往不具备可维护性,因为需求的车轮是不断迭代向前的。如果此时产品经理拿出40m
长刀,需要在原先的基础上,再添加一个批量上传原图
的功能。此时你就只能大费周章地修改各种判断条件逻辑,然后化身容嬷嬷,拿着针对产品经理说,你看我扎不扎你就完了。
其实这些问题是可以用更好的方法去规避的,这个方法就是使用设计模式中的策略模式的思想去解决问题。而在我们前端小组的日常开发中,这种思想也是我们最常用的设计模式思想。那么该如何解决问题呢?
`定义计算属性:`
props: {
type: {
type: String,
default: 'knifeLayout'
}
},
computed: {
`这个计算属性就是我们当前需要处理的对象,包含当前需要使用的各种字段`
`后续如果需要添加N个功能,都可以继续往下添加配置项,这样写出来的代码维护性较高`
option({ type }) {
return {
knifeLayout: {
title: '批量上传刀版图',
key: 'knifePath',
fun: productApi.updateknife,
showBtn: true,
`策略模式中,也可以定义函数,比如:`
beforeOpen: () => { return true }
},
showImage: {
title: '批量上传效果图',
key: 'showImagePath',
`xxxApi`
fun: productApi.updateShowImage,
showBtn: false
}
}[type]
}
}
Tips:
永远不要使用变量 == 字符串
这样的代码作为判断条件,因为字符串是可变的。 比如在上述栗子中,如果我们使用按钮名称去判断当前状态。而产品后续如果需要修改按钮名称,那么我们也需要修改对应的判断条件,这种方式是不可取的,总不能满头黑线呆在原地画个圈圈吧。遇到这种情况,最好的解决方式是,使用唯一属性去作为判断,这种属性不会因为外部因素的变化而变化,最具备稳健性。 那么对应这个案例最好的解决方式是,添加计算属性,对父组件传递来的自定义type
进行判断。因为这个prop
值是我们自定义的,所以代码会比较稳定。
b. 栗子2
`使用策略模式的思想,定义对象映射表,简化代码:`
computed: {
message({ activeName }) {
const mappingList = {
1: '不同产品组合',
2: '相同产品组合(图案相同)',
3: '相同产品组合(图案不同)',
}
return mappingList[activeName]
}
}
c. 栗子3 (栗子2的变种——多层判断
)
假定有这么一个应用场景:平台字段管理详情路由上有type
(标识新增或者编辑),platId
(标识是属于哪个平台)。对于速卖通和亚马逊平台的新增和编辑,需要请求不同的接口。
如果按照栗子2
的写法,得到的结果会是:
`AliExpress, Amazon为平台id常量,isEdit为根据type得到的计算属性`
const { fun1, fun2, fun3, fun4 } = fieldApi
const mappingList = {
[AliExpress]: this.isEdit ? fun1 : fun2,
[Amazon]: this.isEdit ? fun3 : fun4
}
const fun = mappingList[platId]
栗子2
的写法其实是不具备可扩展性的。如果我们不止有新增或者编辑这两个功能,比如多出一个复制的功能,上述代码就要重构了,是不利于后期维护的。为此,我们可以通过多层判断来完善栗子2
的写法:
const { fun1, fun2, fun3, fun4, fun5, fun6 } = fieldApi
`方法一(按店铺划分):`
const mappingList = {
[AliExpress]: {
edit: fun1,
add: fun2,
`新增的功能只需要往下写就好了:`
copy: fun5
},
[Amazon]: {
edit: fun3,
add: fun4,
`新增的功能只需要往下写就好了:`
copy: fun6
}
}
const fun = mappingList[platId][type]
`方法二(按type划分):`
const mappingList = {
edit: {
[AliExpress]: fun1,
[Amazon]: fun3
},
add: {
[AliExpress]: fun2,
[Amazon]: fun4
},
`新增的功能只需要往下写就好了:`
copy: {
[AliExpress]: fun5,
[Amazon]: fun6
}
`...`
}
const fun = mappingList[type][platId]
d. 栗子4
`借用el-button的组件封装思想,这其实也是利用策略模式,不同按钮根据type值给予动态类名。`
`然后单独分别对这些按钮类的样式进行修改, 后续如果需要添加类名也可以继续往下设置`
`也因为如此,同一个组件就可以拥有不同样式`
`template:`
<div class="search-radio-group" :class="`search-radio-group--${theme}`">
</div>
`script`
props: {
theme: {
type: String,
default: 'default'
}
}
`scss`
.search-radio-group--default {
`此处写默认的样式`
}
.search-radio-group--border {
`此处写带有边框的样式`
}
`后续如果需要增加额外的主题样式,以此格式继续往下配置即可`
2. 观察者模式 vs
发布订阅模式
观察者模式是发布订阅模式的一种特殊形式,但两者并不完全相同。言简意赅、一针见血来说,观察者模式是去嘉丽敦公司上班,而发布订阅模式是给嘉丽敦公司做外包。
观察者模式(发布者知道观察者的存在
)
-
发布者:
a. 存在添加订阅者的方法,为多个订阅者提供订阅功能;
b. 在自身发生改变时,会将变化同时通知给多个订阅者;
-
订阅者:
在接收到发布者的变化后,会执行自己的一套方法,对变化做出响应。
`observe.js`
`定义订阅者`
export class Observer {
constructor(name) {
this.name = name
}
update(cb) {
cb(this.name)
}
}
`定义发布者`
export class Subject {
constructor() {
this.observerList = []
}
`添加订阅方法`
addObserver(observer) {
this.observerList.push(observer)
}
notify(cb) {
console.log('那个会唱跳rap的男人要开演唱会了!')
this.observerList.forEach((observer) => observer.update(cb))
}
`取消订阅方法`
unsubscribe(observer) {
console.log(`${observer}取消订阅啦`)
const cbIndex = this.observerList.findIndex(({ name }) => name === observer)
cbIndex != -1 && this.observerList.splice(cbIndex, 1)
}
`全部取消订阅方法`
unsubscribeAll() {
console.log('已全部取消订阅')
if (this.observerList.length) this.observerList = []
}
}
<template>
<div class="app-container">
<el-button type="primary" size="small" @click="clickHandler">观察者模式</el-button>
</div>
</template>
<script>
import { Observer, Subject } from './module/observe.js'
export default {
methods: {
clickHandler() {
`创建发布者`
const subject = new Subject()
`创建订阅者`
const person1 = new Observer('渣渣辉1')
const person2 = new Observer('渣渣辉2')
const person3 = new Observer('渣渣辉3')
`发布者添加需要监听的订阅者`
subject.addObserver(person1)
subject.addObserver(person2)
subject.addObserver(person3)
`发布者通知`
subject.notify((person) => {
console.log(`${person}: 那个会唱跳rap的男人是谁? 我都没听说过。不听不听,不如贪玩蓝月,一刀999`)
})
subject.notify((person) => {
console.log(`${person}: 原来是cxk,社会我坤哥,我必须得去捧场!`)
})
subject.unsubscribe('渣渣辉1')
subject.notify((person) => {
console.log(`${person}: 社会我坤哥,人帅动作多`)
})
subject.unsubscribeAll()
subject.notify((person) => {
console.log(`${person}: 社会我坤哥,人帅动作多`)
})
}
}
}
</script>
发布订阅模式(由任务中心统一管理,发布者不清楚订阅者的存在
)
不同于观察者模式,在发布订阅模式中,多出一个类似中间商的事件调度中心
角色。发布者
和订阅者
之间互不认识,老死不相往来,通过事件调度中心
进行交流。举栗来说,最近狂飙电视剧爆火,下班后你拖着疲惫的身体,就想一睹狂飙的剧情。那么你会如何处理呢?首先你会登录奇异果平台,在平台上关注并订阅狂飙电视剧的消息。当狂飙电视剧有更新时,出品方会将最新的剧集授权给奇异果平台,平台会以手机短信的方式提醒你电视剧更新了。那么在这个过程中,狂飙出品方就是发布者,关注狂飙电视剧的用户就是订阅者,而充当中介的奇异果就是事件调度中心。 出品方不会直接提示用户电视剧有更新,而用户也只能从平台上观察到剧集的变化,用户的订阅和发布者的通知都在平台上实现。
`定义事件调度中心:`
export class EventCenter {
constructor() {
this.observerList = []
}
// 订阅方法
addObserver(observer) {
this.observerList.push(observer)
}
// 发布方法
notify(cb) {
console.log('那个会唱跳rap的男人要开演唱会了!')
this.observerList.forEach((observer) => cb(observer))
}
// 取消订阅方法
unsubscribe(observer) {
console.log(`${observer}取消订阅啦`)
const cbIndex = this.observerList.findIndex((e) => e === observer)
cbIndex != -1 && this.observerList.splice(cbIndex, 1)
}
unsubscribeAll() {
console.log('已全部取消订阅')
if (this.observerList.length) this.observerList = []
}
}
<template>
<div class="app-container">
<el-button type="primary" size="small" @click="clickHandler">发布订阅模式</el-button>
</div>
</template>
<script>
import { EventCenter } from './module/eventCenter.js'
export default {
methods: {
clickHandler() {
const eventCenter = new EventCenter()
eventCenter.addObserver('渣渣辉1')
eventCenter.addObserver('渣渣辉2')
eventCenter.addObserver('渣渣辉3')
eventCenter.notify((person) => {
console.log(`${person}: 那个会唱跳rap的男人是谁? 我都没听说过。不听不听,不如贪玩蓝月,一刀999`)
})
eventCenter.notify((person) => {
console.log(`${person}: 原来是cxk,社会我坤哥,我必须得去捧场!`)
})
eventCenter.unsubscribe('渣渣辉1')
eventCenter.notify((person) => {
console.log(`${person}: 社会我坤哥,人帅动作多`)
})
eventCenter.unsubscribeAll()
eventCenter.notify((person) => {
console.log(`${person}: 社会我坤哥,人帅动作多`)
})
}
}
}
</script>
结语
大概就这样吧~