1. if...else真的好用吗? - 策略模式重构
1.1 场景介绍
在项目起步阶段,功能还比较少且不完善,开发以及产品童鞋都处于摸索阶段。由于项目涉及的业务具有专业性,为了讲述方便以及读者理解方便,我将用代称进行通俗易懂地描述。
整个项目是围绕着资源进行拓展的,资源是:父、子 。 项目中遇到判断资源的时候,需要判断,这还不简单? 直接 if...else 啊!于是,十几个页面里,都写了这种用起来非常轻松愉悦的代码。
忽然有一天,产品童鞋调研摸索发现“不行啊,父、子 不够用,我们还得加个爷爷”。啥?加个爷爷?那就if...else if ... else 嘛。简单,立马给你加!十几个页面一起加,也不是...特别难吧?
忽然又有一天,产品童鞋提出“不是所有页面都是爷、父、子,有些页面是只有爷、父”。啥? 那也好像不是特别难... 就把某些页面的if... else if ... else 改成 if... else if... 就好了吧。
if (type === 'grandfathers') {
// do something
} else if (type === 'fathers') {
// do something
} else {
// do something
}
1.2 代码改写
项目慢慢变得越来越庞大,开始弄不清哪些页面是爷、父,哪些页面是爷、父、子。于是我决定用策略模式开始重写部分代码。策略模式,核心就是封装一个策略对象。我们可以简化成下列的写法:
const strategy = {
grandfathers: function() {
// do something
},
fathers: function() {
// do something
},
sons: function() {
// do something
}
}
// type表示当前的资源是grandfathers/fathers/sons:
strategy[type]()
用策略模式重构完上面的代码后,项目变得更加清晰,再也不用担心产品童鞋改需求了。并且由此我深刻地体会到了一件事:只要不是确定是对立面的东西,尽量避免用if...else,因为你永远没办法确定你将来会加多少个else if(捂脸)。
2. 发布-订阅模式
2.1 场景介绍
不知不觉,项目已经到达20几个页面了,三个页面用来分别管理“爷、父、孙”列表,其他页面依赖这些列表可以做选择,页面图大致如下:
举两个栗子:
场景A. 当内容部分展示的是父管理列表页面时,会有正常的【增删改查】。
场景B. 进入页面1时会展示所有的父,用来展示。
场景A我们不说,增删改查太简单了。当增加父的入口只有父管理列表时,实现场景B也完全无压力,就正常地在进入页面时获取一个父列表就可以了。
忽然某一天,产品童鞋说“我们要在侧边栏加一个新增爷父孙的入口”。哦?新增,很简单嘛,新增的弹框调出来就行了吧。
写着写着开始觉得一些页面的交互不对,新增之后,如果页面不刷新,是不会在场景A里刷新列表的,场景B也不会出现新增的父。
开始我是这样写的: 在vuex里用文件名为resource.js维护一份这个新增的状态,我用grandfathersRefreshTs、fathersRefreshTs、sonsRefreshTs来标记新增的时间戳。如果是从侧边栏调的新增弹框,则更新这个时间戳。在页面上读取时间戳,watch它的变化来做一些刷新操作。
对于场景A来说,当内容页面时父管理页面,且在侧边栏点击了【新增】,监听fathersRefreshTs的更新,判断到新增后重新获取父管理列表就行。
对于场景B也是如此。
export default {
// 省略一些代码...
computed: {
...mapState("resource", ["fathersRefreshTs"])
},
watch: {
fathersRefreshTs: {
immediate: true,
handler(val) {
// 在这里做刷新后的列表重新获取资源操作
// 其他操作
}
}
}
}
场景A、B的刷新问题得到了解决,同时在其他二十几个页面也逐一排查了相关问题,并一一修复。
在产品童鞋的辛勤探索之下,业务又有了进一步的发展。项目需要做这些事情:
业务1. 增加一个爷的时候,后端童鞋得通过自动生成一个父和子。
业务2. 某些页面需要保持用户离开时的样子,也就是前端对某些页面使用keep-alive(上面提到的场景A不需要keep-alive,场景B需要keep-alive)
实现业务1时,对于上面提到的场景A,因为场景A不需要keep-alive,所以新增入口只有一个侧边栏和本页面。当前内容展示父管理页面时,监听的fatherRefreshTs依赖三个场景:① 侧边栏点击增加爷的时候,更新父列表。 ② 侧边栏点击增加父的时候,更新父列表。③当前页面内容里进行对父的增删改的时候,更新父列表。
实现业务2时,对于上面提到的使用了keep-alive的场景B,改变fatherRefreshTs的地方就变得更多:① 侧边栏的新增爷 ② 侧边栏的新增爷 ③ 爷管理页面增删改爷列表 ④ 父管理页面增删改父列表
在这里公用一份fatherRefreshTs,将所有改动父列表的地方都通过vuex去改变fatherRefreshTs的值也可以,毕竟一些页面不在当前在没有keep-alive的时候,资源只和一进入页面时的获取的列表还有侧边栏的有关,而当跳转到其他页面时这个页面就destroyed了。就算这个值变化了,也不会触发。
在这种情况下,页面开始变得很难维护且耦合。作为code reviewer,增删改一个爷/父/子我都得去看看这个页面是否有keep-alive,grandfathersRefreshTs、fathersRefreshTs、sonsRefreshTs,而查看它们的值改变的地方在侧边栏、爷管理页面、父管理页面、子管理页面、 就得查看大量的代码。
2.1 代码重构
我参考了发布-订阅模式重写了部分的逻辑,在全局挂载了一个eventBus,eventBus直接封装vue实现的发布订阅,代码如下:
// bus.js
function install(Vue) {
const Bus = new Vue({
methods: {
on(event, callback) {
this.$on(event, callback);
},
emit(event, ...params) {
this.$emit(event, ...params);
},
off(event, callback) {
this.$off(event, callback);
}
}
});
Vue.prototype.$bus = Bus;
}
export default install;
挂在到全局
// main.js
import Vue from "vue";
import App from "./App.vue";
import eventBus from "./utils/observer";
eventBus(Vue);
new Vue({
render: (h) => h(App)
}).$mount("#app");
在侧边栏点击创建父时,发出一个事件:
this.$bus.emit("aside-create-father");
// this.$bus.emit("aside-create-grandfather"); // 如果是新增爷就用这一行
在场景A的父管理列表可以监听事件
// FatherManagement.vue
<script>
export default {
name: "FatherManagement",
created() {
this.$bus.on("aside-create-father", this.handler);
this.$bus.on("aside-create-grandfather", this.handler);
},
methods: {
handler(...params) {
console.log(params);
},
},
};
</script>
场景B也同理。在对应逻辑里将对应事件抛出,然后在用到的页面监听。
在重构完一小部分页面后,体会到的是,不用维护多份状态使bug出现的几率大大降低。同时,因为大大降低了代码耦合度,减轻了code reviewer的负担,对于新进项目的童鞋来说,也更容易上手项目,不再需要看大量的业务代码才能弄明白哪些地方触发了资源的变动。