大家好,今天的分享由我们团队的 uncle13 老师提供。
“封装业务组件”,想必是大部分同学会在简历中提及的,很多同学也会作为个人亮点向面试官介绍。但当面试官问起“你做了哪些业务组件的封装?为什么要封装?怎么封装的?”之类的问题,你的回答是否能让面试官满意呢?
业务组件(Business Component)广义上的定义:一个可以独立运行的构建企业系统或功能模块。在这种定义下,业务组件被认为是实现特定目标所需的一组紧密关联的工作事项的合集。
前端视角下,业务组件封装是一种将常用的业务逻辑和UI组件结合起来,以便在项目中重复使用的方式。这样可以提高代码的复用性、可维护性和开发效率。
首先我们通过一个简单的示例说明一下,假设我们要封装一个名为OrderList
的业务组件,在电商网站中展示用户的订单列表:
OrderList.vue
:<template>
<div class="order-list">
<h3>My Orders</h3>
<ul>
<li v-for="order in orders" :key="order.id">
<span>{{ order.date }}</span>
<span>{{ order.total }}</span>
<!-- 其他订单信息显示 -->
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
orders: {
type: Array,
required: true,
},
},
};
</script>
<style>
.order-list {
/* 添加样式 */
}
</style>
在上述代码中,我们创建了一个名为OrderList
的组件,该组件接收一个名为orders
的prop,它是一个订单对象数组。通过使用v-for
指令,我们将每个订单的相关信息渲染到页面上。
OrderList
:<template>
<div>
<order-list :orders="userOrders"></order-list>
</div>
</template>
<script>
import OrderList from './OrderList.vue';
export default {
components: {
OrderList,
},
data() {
return {
userOrders: [
{ id: 1, date: '2022-01-01', total: 100 },
{ id: 2, date: '2022-01-02', total: 200 },
// 其他订单数据
],
};
},
};
</script>
在这个示例中,我们引入了刚才创建的OrderList
组件,并在模板中使用它。通过传递名为userOrders
的数据给orders
属性,我们将用户的订单数据传递给封装的组件进行展示。
通过业务组件的封装,我们可以将常用的业务逻辑和UI组件结合起来,实现代码的复用和集中管理。这样可以提高开发效率,同时也有利于维护和项目的扩展。根据具体的业务需求,我们还可以添加更多的功能、参数和样式定制,以满足不同场景的要求。
业务组件在前端开发中具有以下特点:
可重用性:
独立性:
可配置性:
可组合性:
可测试性:
文档化和维护:
通过利用这些特点,业务组件能够提高前端开发的效率和可维护性。它们使得开发人员能够将精力集中在组件的核心功能上,并构建出具有灵活性和可重用性的前端应用程序。
在进行业务组件封装时,可以遵循以下设计原则:
具体的示例:我们将创建一个名为InputField
的业务组件,用于接收用户的输入并进行验证。该组件将根据验证结果显示错误信息。
<template>
<div class="input-field">
<label>{{ label }}</label>
<input v-model="inputValue" @blur="validateInput" />
<span v-if="error" class="error-message">{{ error }}</span>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
required: true,
},
validationRegex: {
type: RegExp,
required: true,
},
},
data() {
return {
inputValue: '',
error: '',
};
},
methods: {
validateInput() {
if (!this.inputValue.match(this.validationRegex)) {
this.error = 'Invalid input';
} else {
this.error = '';
}
},
},
};
</script>
<style>
.input-field {
/* 添加样式 */
}
.error-message {
color: red;
}
</style>
在上述示例中,我们创建了一个名为InputField
的组件,它接收两个prop:label
和validationRegex
。label
用于显示输入字段的标签,validationRegex
是一个正则表达式,用于验证用户的输入。
在模板中,我们使用v-model
指令将用户的输入与inputValue
属性进行双向绑定。通过监听@blur
事件,当输入框失去焦点时调用validateInput
方法进行输入验证。
在validateInput
方法中,我们使用正则表达式对用户的输入进行验证。如果输入不符合要求,则将错误信息存储在error
属性中,并在页面上显示错误信息。如果输入有效,则将error
设置为空。
通过这个示例,我们封装了一个通用的输入字段组件,它可以接受各种验证规则,并根据用户的输入显示相应的错误信息。这样可以提高代码的复用性和可维护性,在不同的项目中重复使用该组件,同时也增强了用户体验和数据的准确性。
具体的示例:假设我们正在开发一个名为UserCard
的用户卡片组件,用于显示用户的个人信息和操作按钮。我们可以通过将相关的功能放在一起,并尽量减少与其他组件的依赖来实现高内聚和低耦合。
<template>
<div class="user-card">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button @click="sendMessage">Send Message</button>
<button @click="deleteUser">Delete User</button>
</div>
</template>
<script>
export default {
props: {
user: {
type: Object,
required: true,
},
},
methods: {
sendMessage() {
// 发送消息的逻辑
},
deleteUser() {
// 删除用户的逻辑
},
},
};
</script>
<style scoped>
.user-card {
/* 添加样式 */
}
</style>
在上述示例中,UserCard
组件具有高内聚性和低耦合性的特点:
高内聚:将所有与用户卡片相关的代码集中在一起。在模板中,我们使用插值表达式将用户的姓名和电子邮件显示在页面上。而发送消息和删除用户的功能都封装在sendMessage
和deleteUser
方法中。
低耦合:UserCard
组件只依赖一个名为user
的prop,该prop包含了需要展示的用户信息。这样,在其他地方使用UserCard
组件时,只需要传递相应的user
对象即可,而不需要关心组件内部的具体实现细节。
通过将相关的代码放在一起,并减少与其他组件的依赖,我们实现了高内聚低耦合的设计。这样可以提高组件的独立性、可维护性和可测试性。如果未来需要修改发送消息或删除用户的逻辑,我们只需关注sendMessage
和deleteUser
方法,而不会影响到其他部分的代码。同时,通过将UserCard
组件解耦,我们可以更灵活地在不同的项目或页面中重用该组件。
具体的示例:一个名为Tabs的业务组件可以允许用户自定义选项卡的样式、布局和行为。通过提供多个配置选项,如样式类名、位置和动画效果等,以满足不同项目的需求。
<template>
<div class="tabs">
<ul :class="tabListClass">
<li v-for="(tab, index) in tabs"
:key="index"
:class="{ active: activeTabIndex === index }"
@click="changeTab(index)">
{{ tab.label }}
</li>
</ul>
<div class="tab-content">
<slot :name="activeTabKey"></slot>
</div>
</div>
</template>
<script>
export default {
props: {
tabs: {
type: Array,
required: true,
},
tabListClass: {
type: String,
default: 'default-tab-list', // 默认样式类名
},
},
data() {
return {
activeTabIndex: 0,
};
},
computed: {
activeTabKey() {
return this.tabs[this.activeTabIndex].key;
},
},
methods: {
changeTab(index) {
this.activeTabIndex = index;
},
},
};
</script>
<style scoped>
.tabs {
/* 添加样式 */
}
.default-tab-list {
/* 默认选项卡样式 */
}
</style>
在上述示例中,Tabs
组件允许用户通过提供tabs
和tabListClass
两个props来自定义选项卡的样式、布局和行为。
tabs
prop 是一个包含选项卡信息的数组。每个选项卡对象应该具有一个label
属性表示标签的显示文本,以及一个key
属性表示标签的唯一标识。
tabListClass
prop 是一个字符串类型,用于设置选项卡列表的样式类名。如果用户未提供该props,默认使用default-tab-list
作为样式类名。
在模板中,我们使用v-for
指令遍历tabs
数组,为每个选项卡生成一个li
元素。通过绑定样式类和点击事件,我们可以根据activeTabIndex
来控制当前活动(选中)的选项卡。
通过使用插槽,我们可以将选项卡对应的内容显示在组件内部的.tab-content
元素中。插槽的名称是根据当前活动选项卡的key
动态生成的。
通过这种方式,用户可以自定义选项卡的样式类名、布局和行为。他们可以传递不同的tabs
数组和tabListClass
值,以满足不同项目的需求。这种可配置和可扩展的设计使得Tabs
组件更加灵活和易于复用,在不同的场景下都能适应不同的样式和需求。
具体的示例:假设我们正在开发一个名为LoginForm
的登录表单组件,我们希望用户可以轻松地使用和理解该组件。
<template>
<form class="login-form" @submit.prevent="submitForm">
<label for="username">Username</label>
<input type="text" id="username" v-model="username" />
<label for="password">Password</label>
<input type="password" id="password" v-model="password" />
<button type="submit">Login</button>
</form>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
submitForm() {
// 处理表单提交的逻辑
const user = {
username: this.username,
password: this.password,
};
// 提交用户凭据或执行其他操作
},
},
};
</script>
<style scoped>
.login-form {
/* 添加样式 */
}
</style>
在上述示例中,LoginForm
组件易于使用和理解,具有以下特点:
简洁明了:表单结构清晰简洁,只包含用户名、密码输入框和登录按钮。这使得用户可以快速理解组件的功能,并且不会感到困惑。
直观易懂:在模板中,我们使用v-model
指令将输入框与组件的username
和password
数据属性进行双向绑定。这样用户输入的内容会及时反映在组件内部的数据中。
易于交互:通过监听表单的submit
事件,在提交表单时调用submitForm
方法处理登录逻辑。用户只需要填写用户名和密码并点击登录按钮即可完成登录操作。
简单样式:为了保持简洁性,示例代码没有提供过多的样式。根据实际需求,可以添加适当的样式来增强用户界面的可视性和吸引力。
通过设计易于使用和理解的组件,用户可以快速上手和操作,减少学习成本和困惑。同时,简洁明了的代码结构和直观的用户界面也使开发人员能够轻松理解和维护组件,提高开发效率和代码质量。
具体的示例:假设我们正在开发一个名为Notification
的通知组件,该组件可以显示系统通知、成功消息或错误信息等。我们希望这个组件既可以作为全局的通知栏,也可以嵌入到其他组件中使用。
<template>
<div class="notification" :class="notificationType">
{{ message }}
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true,
},
type: {
type: String,
default: 'info',
validator(value) {
return ['info', 'success', 'error'].includes(value);
},
},
},
computed: {
notificationType() {
return `notification-${this.type}`;
},
},
};
</script>
<style scoped>
.notification {
/* 添加样式 */
}
.notification-info {
/* 默认样式 */
}
.notification-success {
/* 成功样式 */
}
.notification-error {
/* 错误样式 */
}
</style>
在上述示例中,Notification
组件具有灵活性和复用性的特点:
灵活配置:通过提供message
和type
两个props,用户可以配置通知显示的文本内容和类型。type
属性默认为info
,并通过一个自定义的验证器确保用户提供的类型符合预期。这使得组件可以根据需求显示不同类型的通知,如信息、成功或错误。
可嵌入复用:由于使用了单文件组件的方式,Notification
组件可以直接在其他组件中引用和嵌入,以满足特定页面或组件的通知需求。它可以被重复使用,同时又具备灵活配置的特点。
样式分离:通过添加带有特定类型前缀的样式类,我们为每个通知类型定义了相应的样式。这种样式分离的设计使得组件的外观易于修改和扩展,同时也遵循了单一责任原则。
通过这种设计,我们使Notification
组件具有灵活性和复用性,可以在不同的场景中进行配置和使用。用户可以根据需要传递不同的message
和type
值来定制通知的内容和样式。同时,通过将通知的显示和样式逻辑封装在组件内部,我们实现了高内聚低耦合的设计,提高了组件的可维护性和扩展性。
具体的示例:一个表单输入组件要能够对用户的输入进行验证和处理,并提供合适的错误提示。无论用户输入是空值、非法字符还是格式错误,组件都应该能够正常运行,并提供友好的错误信息。
<template>
<div class="form-input">
<input :value="inputValue" @input="updateInputValue" />
<span class="error-message" v-if="hasError">{{ errorMessage }}</span>
</div>
</template>
<script>
export default {
data() {
return {
inputValue: '',
error: false,
errorMessage: '',
};
},
computed: {
hasError() {
return this.error && this.errorMessage !== '';
},
},
methods: {
updateInputValue(event) {
const value = event.target.value;
// 进行输入验证和处理逻辑
if (value === '') {
this.error = true;
this.errorMessage = '请输入内容';
} else if (!/^[a-zA-Z0-9]+$/.test(value)) {
this.error = true;
this.errorMessage = '只能输入字母和数字';
} else {
this.error = false;
this.errorMessage = '';
}
this.inputValue = value;
},
},
};
</script>
<style scoped>
.form-input {
/* 添加样式 */
}
.error-message {
color: red;
}
</style>
在上述示例中,FormInput
组件具有健壮性和容错性的特点:
输入验证和处理:通过监听输入框的input
事件,我们可以实时获取用户输入,并进行相应的验证和处理逻辑。在示例中,我们对输入值进行了两个简单的验证:空值和非法字符。根据验证结果,我们设置error
属性来标记是否出现错误,并设置errorMessage
属性来存储错误提示信息。
友好的错误提示:在模板中,我们使用条件渲染来显示错误提示信息。当error
为true
且errorMessage
不为空时,错误提示会显示在组件下方。这样用户可以清晰地看到输入的错误并得到相应的提示。
容错机制:无论用户输入是空值、非法字符还是格式错误,组件都能够正常运行并提供友好的错误信息。即使出现错误,用户仍然可以继续输入并进行验证,直到输入符合要求为止。
通过这种设计,我们实现了健壮性和容错性的表单输入组件。它可以对用户的输入进行验证和处理,同时提供合适的错误提示。这样可以增加用户体验和可靠性,防止因用户输入错误而导致整个应用程序崩溃或产生其他问题。
具体的示例:一个列表渲染的组件在处理大量数据时可能面临性能问题。通过虚拟滚动技术,只渲染可见区域的数据,避免不必要的渲染操作,从而提高组件的性能和响应速度。
<template>
<div class="virtual-list" ref="listContainer" @scroll="handleScroll">
<div :style="{ height: totalHeight + 'px' }"></div>
<div class="viewport" :style="{ height: viewportHeight + 'px' }">
<div
v-for="(item, index) in visibleItems"
:key="index"
:style="{ transform: `translateY(${item.offsetTop}px)` }"
>
{{ item.text }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [], // 所有数据项
visibleItems: [], // 可见的数据项
totalHeight: 0,
viewportHeight: 0,
itemHeight: 30, // 每个数据项的高度
};
},
mounted() {
this.calculateVisibleItems();
this.handleScroll();
},
methods: {
calculateVisibleItems() {
const container = this.$refs.listContainer;
const scrollTop = container.scrollTop;
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(this.viewportHeight / this.itemHeight),
this.items.length - 1
);
this.visibleItems = this.items.slice(startIndex, endIndex + 1);
},
handleScroll() {
const container = this.$refs.listContainer;
this.calculateVisibleItems();
container.scrollTop % this.itemHeight === 0 && this.$forceUpdate();
},
},
};
</script>
<style scoped>
.virtual-list {
overflow-y: auto;
position: relative;
}
.viewport {
position: absolute;
top: 0;
left: 0;
right: 0;
overflow: hidden;
}
</style>
在上述示例中,VirtualList
组件利用虚拟滚动技术实现了只渲染可见区域的数据项:
listContainer
元素是包含整个列表的容器,通过监听其滚动事件来触发可视区域计算。calculateVisibleItems()
方法中,根据容器的滚动位置和可视区域的高度计算出可见的数据项的起始和结束索引,从而确定需要渲染的数据范围。v-for
指令遍历可见数据项,通过设置样式的transform
属性来调整每个数据项的位置,实现滚动效果。通过使用虚拟滚动技术,VirtualList
组件在处理大量数据时只渲染可见区域的数据项,避免了渲染整个列表的性能问题。这样可以提高组件的性能和响应速度,使用户在滚动列表时能够获得更流畅的体验。
具体的示例:一个名为DatePicker的日期选择组件通过良好的模块化设计和依赖注入,使得它易于进行单元测试。可以针对不同的情况编写测试用例,验证组件正确处理用户的选择和返回的日期数据。
// DatePicker.js
class DatePicker {
constructor(dateService) {
this.dateService = dateService;
this.selectedDate = null;
}
selectDate(date) {
this.selectedDate = date;
}
getFormattedDate() {
return this.dateService.format(this.selectedDate);
}
}
// DateService.js
class DateService {
format(date) {
// 实际的日期格式化逻辑
}
}
在上述示例中,DatePicker
组件通过依赖注入DateService
来处理日期选择和格式化:
DatePicker
类的构造函数接受一个dateService
参数,这样可以将具体的日期服务实现注入到组件中。这种依赖注入的方式使得在测试时可以方便地替换日期服务实现为测试用例提供假数据或模拟行为。
selectDate
方法用于处理用户的选择,并将选择的日期存储在selectedDate
属性中。而getFormattedDate
方法使用了依赖的dateService
来对选择的日期进行格式化。通过这种方式,我们将日期相关的逻辑解耦出来,使其易于测试并提高可维护性。
通过使用依赖注入和模块化设计,DatePicker
组件具有良好的可测试性。我们可以编写针对不同情况的测试用例,验证组件在处理用户选择和返回日期数据时的正确性。同时,我们还可以轻松地替换日期服务的实现,以满足不同测试需求或模拟各种日期场景,从而完善测试覆盖范围。
再给我们的辅导服务打个广告,我们目前有面试全流程辅导、简历指导、模拟面试、零基础辅导和付费咨询等增值服务,大厂前端专家一对一辅导。
辅导服务推出了近 2 年的时间,已助力超过 200 + 的同学找到心仪的工作,感兴趣的伙伴可以联系小助手(微信号:interview-fe2)了解详情哦~