前言
大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心。
最近接手了一个老项目,我一看提交,天啊足足有五年的历史了,里面的东西都挺原始的,老大要求我在做这个项目需求的过程中,看能不能尽量地去顺便优化一下~好吧,那就优化吧~于是乎就先从一些公用的东西先优化,主要是用了一些设计模式的思想
虽然简单,但是还是挺有意义的~
策略模式 -> 表单校验更方便
背景
我发现项目中很多地方在提交表单时,做校验的方式非常地直接了当,例如
const verifyForm = (formData) => {
if (formData.userName == '') {
console.log('用户名不能为空');
return false
};
if (formData.password.length < 6) {
console.log('密码长度不能小于6位');
return false;
}
if ((!/(^1[3|5|8][0-9]{9}$)/.test(formData.phone)) {
console.log('手机格式错误');
return false
}
}
而且这么写的地方非常多,这导致很多逻辑都重复了,所以我想使用策略模式再加上一些必要的封装,去让代码复用性更强,更好维护
改造的设想
以下是伪代码,具体得自己去实现
我的改造设想是这样的
const validator = new Validator(formData)
const validators = [
{
name: '用户名',
field: 'userName',
validator: 'isNonEmpty'
},
{
name: '密码',
field: 'password',
validator: 'length:6'
},
{
name: '手机',
field: 'phone',
validator: 'phone'
}
]
for (let v of validators) {
validator.add(v)
}
validator.start().then(() => {
// 校验通过
}).catch((err) => {
// 校验不通过
})
其实就是维护一个 Validator 的类,专门用来进行表单的校验,肯定会有人反驳说,你看这代码不是更多了吗?但是这样做的好处是很大的
- 可维护性强: 有一天需要改校验规则,只需要改一处,不需要每个页面都去改
- 复用性强: 比较统一,防止你自己一个页面一个页面去敲的时候敲错了
代码实现
那接下来就开始代码实现吧,核心就是实现 Validator 这个类,利用策略模式的思想,让校验更加地方便快捷高质量~
// 维护一个规则的map
const ruleMap = {
isNonEmpty: (name, v) => {
if (v === '') {
return `${name}不能为空`
}
},
length: (name, v, len) => {
if (v.length < len) {
return `${name}不能少于${len}位`
}
},
phone: (name, v) => {
if (!/(^1[3|5|8][0-9]{9}$)/.test(v)) {
return `${name}不是一个手机号`
}
}
}
class Validator {
// 记录校验结果
result = [];
constructor(formData) {
this.formData = formData;
}
add({
name,
field,
validator
}) {
const formItem = this.formData[field];
// 区分length与其他规则
if (validator.includes('length')) {
const va = validator.split(':')
result.push(ruleMap['length'](name, formItem, va[1]))
} else {
result.push(ruleMap[validator](name, formItem))
}
}
start() {
return new Promise((resolve, reject) => {
const fails = this.result.filter((v) => v)
if (fails.length) {
// 失败走catch
reject(fails)
} else {
// 成功走then
resolve(true)
}
})
}
}
适配器模式 -> 适配多种数据格式
背景
项目中有很多页面,页面中包括了:
- 列表
- 下拉框
- 多选框
这看似是三种数据格式,但是其实,这三个数据,是通过同一个接口返回的数据来生成的。。。
所以很多地方都是这么写:
list = [];
selectOptions = [];
checkedOptions = [];
http().then((res) => {
// 处理成列表格式
this.list = xxxxx;
// 处理成下拉框格式
this.selectOptions = sssss;
// 处理成多选框格式;
this.checkedOptions = ccccc;
})
上面省略了许多代码,实际上很多页面的逻辑都是一样的,如果这样写的话导致很多重复的代码,非常冗余
改造的设想
我设想的是实现一个适配器的类,类上拥有一些方法, 可以将数据源转换成所需要的格式
class Adpater {
dataSouce = [];
constructor(dataSouce) {
this.dataSouce = dataSouce;
}
transformToList() {
// 转换成列表数据格式
}
transformToSelectOptions() {
// 转换成下拉框数据格式
}
transformToCheckedOptions() {
// 转换成多选框数据格式
}
}
代码实现
其实代码实现也挺简单的,实现不难,能让代码更加精简,复用性更强
class Adpater {
dataSouce = [];
constructor(dataSouce) {
this.dataSouce = dataSouce;
}
transformToList(columns) {
// 转换成列表数据格式
return this.dataSouce.map((item) => {
const obj = {}
for (let c of columns) {
const field = c.field
obj[field] = item[field];
}
return obj
})
}
transformToSelectOptions(options) {
// 转换成下拉框数据格式
const { labelField, valueField } = options
return this.dataSouce.map((item) => ({
label: item[labelField],
value: item[valueField]
}))
}
transformToCheckedOptions(options) {
// 转换成多选框数据格式
const { valueField } = options
return this.dataSouce.map((item) => ({
checked: false,
value: item[valueField]
}))
}
}
使用的时候只需要构建一个 Adapter 即可,使用里面的方法去进行转换
list = [];
selectOptions = [];
checkedOptions = [];
http().then((data) => {
const adapter = new Adapter(data)
// 处理成列表格式
this.list = adapter.transformToList(columns);
// 处理成下拉框格式
this.selectOptions = adapter.transformToSelectOptions({
labelField: 'name',
valueField: 'id'
});
// 处理成多选框格式;
this.checkedOptions = adapter.transformToCheckedOptions({
valueField: 'id'
});;
})
发布订阅模式 -> 两页面间的通信
背景
这个老项目有一个 BUG,当然这个 BUG 不是我写的,而是老 BUG,是这样的有两个页面
- 页面 A:有同步代码,有异步代码
- 页面 B:全是同步代码
注意:此项目是老项目,没有全局状态管理工具!!!
// 页面A
console.log(1)
console.log(2)
http.get(url).then(res => {
console.log(3)
localStorage.setItem(key, res)
})
// 页面B
console.log(
localStorage.getItem(key)
)
然后这两个页面是先后加载的,那么我们可以得出输出顺序是
1 // 页面A
2 // 页面A
undefined // 页面B
console.log(3) // 页面A
因为请求是异步的,导致页面B那边拿不到 localStorage 里面的东西,而无法完成很多操作,导致了出现 BUG。所以得想想怎么去解决这个 BUG。
定时器
最简单的就是利用定时器去解决
// 页面B
setTimeout(() => {
console.log(
localStorage.getItem(key)
)
})
但是这样是不对的,不好维护,滥用定时器会导致以后可能会有新的 BUG 出现!!!
发布订阅模式
所以还是使用发布订阅,首先实现一个发布订阅中心
以下是简单实现
type Callback<T> = (data: T) => void;
class PubSub<T> {
private subscribers: Callback<T>[] = [];
subscribe(callback: Callback<T>): void {
this.subscribers.push(callback);
}
unsubscribe(callback: Callback<T>): void {
this.subscribers = this.subscribers.filter(fn => fn !== callback);
}
publish(data: T): void {
this.subscribers.forEach(fn => fn(data));
}
}
export const ps = new PubSub();
接着就可以用它来解决我们那个 BUG 了!!
// 页面A
console.log(1)
console.log(2)
http.get(url).then(res => {
console.log(3)
localStorage.setItem(key, res)
ps.publish(res)
})
// 页面B
// 订阅
ps.subscribe((res) => {
console.log(res)
console.log(
localStorage.getItem(key)
)
})
现在的输出顺序就是
1 // 页面A
2 // 页面A
console.log(3) // 页面A
res // 页面B
res // 页面B
结语 & 加学习群 & 摸鱼群
我是林三心
- 一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手;
- 一个偏前端的全干工程师;
- 一个不正经的掘金作者;
- 一个逗比的B站up主;
- 一个不帅的小红书博主;
- 一个喜欢打铁的篮球菜鸟;
- 一个喜欢历史的乏味少年;
- 一个喜欢rap的五音不全弱鸡
如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点