你好,我是alova的作者胡镇。alova是一个请求策略库,一行代码解决复杂的请求问题,访问官网。
今天就想分享点干货出来。
封装一直是一个老生常谈的话题了,什么程序员都绕不开,也是衡量一个程序员水平高低的重要标准。
多年的开发经历告诉我,这是一种对世界认知的体现,而不仅仅只是复用性的问题,理解你就是骨灰级程序员啦。
让我们先来看几个例子,鉴于前端同学较多,我就以前端举例吧。
常见封装问题来一波
我们以几个简单的示例来开始吧,以下几个封装例子都不够好,你来看看存在哪些可以改进的地方吗?
示例1
假设我们有一个业务场景,需要展示一个用户信息卡片,卡片上包含用户的姓名、年龄和头像。同时,根据用户的年龄,卡片上会显示不同的背景颜色,来看一个vue组件。
html
<template>
<div :class="['user-card', ageClass]">
<img :src="user.avatar" alt="User Avatar" />
<h2>{{ user.name }}</h2>
<p>Age: {{ user.age }}</p>
</div>
</template>
<script>
export default {
props: {
user: {
type: Object,
required: true
}
},
computed: {
ageClass() {
if (this.user.age < 18) {
return 'teenager';
} else if (this.user.age < 60) {
return 'adult';
} else {
return 'elderly';
}
}
}
};
</script>
<style scoped>
.user-card {
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.teenager {
background-color: lightblue;
}
.adult {
background-color: lightgreen;
}
.elderly {
background-color: lightpink;
}
</style>
示例1分析
这个组件存在两个问题:
问题1: 内部使用了计算属性ageClass
,来根据用户的年龄返回不同的背景颜色类名。这是一种将业务逻辑(根据年龄设置背景颜色)和展示逻辑混合在了一起,初一看其实没啥问题啊,但这里我想说的是,一切设计都是跟着需求走的,如果其他场景需要展示类似的卡片但不需要根据年龄设置背景颜色,那么这个组件就无法直接复用。当然,也有可能你们产品就说了,我们产品里就这三种情况,你当然可以按上面这样写,但谁又能保证过几天你们产品改变主意,或者换人了呢?
在实际项目中,我们通常会尽量将业务逻辑和展示逻辑分离,以提高代码的复用性和可维护性。
问题2: 用户参数是以object的形式传递的,这一般是某个大哥图方便,直接把接口数据往里扔造成的,实际情况是业务中大多开发人员都不喜欢写文档,对团队其他人来说是难以理解的,他们需要去翻看组件源码,到底包含了啥东西,很痛苦,这里只是举了个简单的name
和age
的例子,实际情况组件更复杂,参数更多。
示例2
我们有一个函数,它旨在验证用户的输入计算一些值,并更新UI。
js
function handleUserInputAndUpdateUI(inputString, uiElement) {
// 验证输入字符串
if (typeof inputString !== 'string' || inputString.trim() === '') {
console.error('Invalid input: Input must be a non-empty string.');
return;
}
// 计算输入字符串的长度
const inputLength = inputString.length;
console.log('Input length:', inputLength);
// 根据输入字符串的长度更新UI
if (uiElement) {
uiElement.innerText = `Input length: ${inputLength}`;
} else {
console.error('UI element is not provided.');
}
}
示例2分析
这个函数的问题是,混合了多个不相关的职责,导致它难以理解,如果你只是为你的某一处表单验证编写这个函数当然没问题,但我们这边说的是封装,也可以理解为粒度太大,下次我们有一个地方也要用到验证输入字符串功能,我们是复制好还是复制好呢?
更好的做法是将这些职责分解为单独的函数,每个函数只负责一个单一职责。这样,代码将更容易阅读、测试和维护。
但上面这段验证输入字符串代码即使单独拆出来还是存在封装的问题,请问你知道是什么吗?评论区说说。
示例3
你有一个组件css是这样的。
css
.my-component {
border: solid 1px #ff3478;
.my-component__button {
color: #ff3478;
border-bottom: solid 1px #ff3478;
&:hover {
background-color: #ff3478;
}
}
.my-component__title {
fontsize: 1.5rem;
color: #ff3478;
}
}
示例3分析
大家能看出上面的css有什么问题吗?
答案是多处使用了#ff3478
这个颜色,但都是硬编码上去的,各位有多少人是这样写过,请点个赞。
好的办法当然是统一到一个css变量里,然后统一引用,这其实是一种小封装,只封装并复用了一个值而不是一个模块,其他例子还有多处操作缓存:
js
// constant.js
const USER_INFO_STORAGE_KEY = 'user_info';
// login.js
localStorage.setItem(USER_INFO_STORAGE_KEY, JSON.stringify(userInfo));
// user.js
const userInfo = JSON.parse(localStorage.getItem(USER_INFO_STORAGE_KEY));
示例4
🌝🌝🌝
html
1 <template>
...
...
...
1253 <template>
1254 <script>
...
...
...
3549 </script>
3550 <style scoped lang="scss">
...
...
...
5263 </style>
示例4分析
一个复杂的组件里密密麻麻写满了代码,就不能分开来写嘛...
封装的三重境界
复用性
在上面的例子中,其实还是以复用性为主的封装,这是我们大多数人所理解的封装,但这只是第一重境界。
职责划分
这点在上面的示例2中有所体现,将一个粒度较大的封装分为职责单一的小块,这听上去怎么还是跟复用性有关,但在实际业务中,你可能会遇到通用组件的各种参数,如何设计这些参数才能保持这个组件的单一职责呢?这是封装的第二层理解。
与机器形成默契
这是对封装的第三层理解,这一层理解是属于认知层面的,我们通过代码与机器交流,本质上和人交流是想通的,当我们跟别人说苹果的时候,别人为什么可以很快就知道你指的是那个"圆圆的有点红红的水果",而不是"黑黑的方块"呢?如果与机器达成这样的共识,是不是不用那么啰嗦它也能知道我们让它干什么呢?
或者,创造一种东西作为连接人与机器的桥梁,桥梁的一边是开发者易于理解的指令,然后转换成机器可以理解的指令,这样可以更好地与机器形成默契。
如果你对这些感兴趣,4月14日(周天)20:00,欢迎来我的直播间共同探讨这些,欢迎你的预约,纯分享啥也不卖
内容大纲
在直播间我们一同来探讨以下的话题,如果你也感兴趣,诚挚邀请你来一起聊聊。
后序
如果想了解alova的话,可以访问官网,在这里,你可以找到更详细的文档和示例代码,帮助你更好地理解和使用这个工具。
有任何问题,你可以加入以下群聊咨询,也可以在github 仓库中发布 Discussions,如果遇到问题,也请在github 的 issues中提交,我们会在最快的时间解决。
同时也欢迎贡献你的一份力量,请移步贡献指南。