面试官:简历中的业务组件封装,你做了哪些工作?

大家好,今天的分享由我们团队的 uncle13 老师提供。


“封装业务组件”,想必是大部分同学会在简历中提及的,很多同学也会作为个人亮点向面试官介绍。但当面试官问起“你做了哪些业务组件的封装?为什么要封装?怎么封装的?”之类的问题,你的回答是否能让面试官满意呢?



一、什么是业务组件

业务组件(Business Component)广义上的定义:一个可以独立运行的构建企业系统或功能模块。在这种定义下,业务组件被认为是实现特定目标所需的一组紧密关联的工作事项的合集。

前端视角下,业务组件封装是一种将常用的业务逻辑和UI组件结合起来,以便在项目中重复使用的方式。这样可以提高代码的复用性、可维护性和开发效率。

首先我们通过一个简单的示例说明一下,假设我们要封装一个名为OrderList的业务组件,在电商网站中展示用户的订单列表:

  1. 创建一个新的Vue组件,例如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: {
      typeArray,
      requiredtrue,
    },
  },
};
</script>

<style>
.order-list {
  /* 添加样式 */
}
</style>

在上述代码中,我们创建了一个名为OrderList的组件,该组件接收一个名为orders的prop,它是一个订单对象数组。通过使用v-for指令,我们将每个订单的相关信息渲染到页面上。

  1. 使用我们封装的组件OrderList
<template>
  <div>
    <order-list :orders="userOrders"></order-list>
  </div>
</template>

<script>
import OrderList from './OrderList.vue';

export default {
  components: {
    OrderList,
  },
  data() {
    return {
      userOrders: [
        { id1date'2022-01-01'total100 },
        { id2date'2022-01-02'total200 },
        // 其他订单数据
      ],
    };
  },
};
</script>

在这个示例中,我们引入了刚才创建的OrderList组件,并在模板中使用它。通过传递名为userOrders的数据给orders属性,我们将用户的订单数据传递给封装的组件进行展示。

通过业务组件的封装,我们可以将常用的业务逻辑和UI组件结合起来,实现代码的复用和集中管理。这样可以提高开发效率,同时也有利于维护和项目的扩展。根据具体的业务需求,我们还可以添加更多的功能、参数和样式定制,以满足不同场景的要求。

二、业务组件有哪些特点?

业务组件在前端开发中具有以下特点:

  1. 可重用性

    • 业务组件是设计和构建为可重用的,可以在应用程序中多次使用。它们独立于特定的业务场景,可以在不同的页面或模块中使用。
    • 通过提高代码的重用性,业务组件能够减少开发时间和工作量,并提高开发效率。
  2. 独立性

    • 业务组件应该是相对独立的,与其他组件或模块之间的耦合度应尽可能低。
    • 这种独立性使得业务组件更容易维护、测试和重构。开发人员可以专注于单个组件的实现和功能,而无需担心整个应用程序的复杂性。
  3. 可配置性

    • 业务组件通常具有可配置的属性(props)或选项,以便根据不同的需求进行定制。
    • 可配置性使得组件能够适应不同的数据和样式要求,从而满足各种业务场景的需要。
  4. 可组合性

    • 业务组件应该是可组合的,可以与其他组件结合使用,形成更复杂的界面或功能。
    • 通过将多个业务组件组合在一起,开发人员可以构建灵活且可扩展的应用程序。
  5. 可测试性

    • 业务组件应该易于测试,以便确保其功能和行为的正确性。
    • 开发人员可以编写单元测试或集成测试来验证组件的各个方面,并确保组件在不同情况下的稳定性。
  6. 文档化和维护

    • 业务组件应该有清晰的文档,描述其使用方法、属性和事件等信息。这样其他开发人员可以轻松理解和使用组件。
    • 组件的代码应遵循良好的编码实践,易于维护和修改。

通过利用这些特点,业务组件能够提高前端开发的效率和可维护性。它们使得开发人员能够将精力集中在组件的核心功能上,并构建出具有灵活性和可重用性的前端应用程序。

三、怎么进行业务组件封装

在进行业务组件封装时,可以遵循以下设计原则:

  1. 单一责任原则(Single Responsibility Principle): 一个业务组件应该只负责一个特定的功能或任务。这有助于保持组件的聚焦和可维护性。

具体的示例:我们将创建一个名为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: {
      typeString,
      requiredtrue,
    },
    validationRegex: {
      typeRegExp,
      requiredtrue,
    },
  },
  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:labelvalidationRegexlabel用于显示输入字段的标签,validationRegex是一个正则表达式,用于验证用户的输入。

在模板中,我们使用v-model指令将用户的输入与inputValue属性进行双向绑定。通过监听@blur事件,当输入框失去焦点时调用validateInput方法进行输入验证。

validateInput方法中,我们使用正则表达式对用户的输入进行验证。如果输入不符合要求,则将错误信息存储在error属性中,并在页面上显示错误信息。如果输入有效,则将error设置为空。

通过这个示例,我们封装了一个通用的输入字段组件,它可以接受各种验证规则,并根据用户的输入显示相应的错误信息。这样可以提高代码的复用性和可维护性,在不同的项目中重复使用该组件,同时也增强了用户体验和数据的准确性。

  1. 高内聚低耦合(High Cohesion and Low Coupling): 组件内部的代码应该具有高内聚性,即相关的代码应该放在一起,实现相同的目标。同时,组件之间应该尽量减少依赖关系,以保持低耦合性,使得组件更加独立和可测试。

具体的示例:假设我们正在开发一个名为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: {
      typeObject,
      requiredtrue,
    },
  },
  methods: {
    sendMessage() {
      // 发送消息的逻辑
    },
    deleteUser() {
      // 删除用户的逻辑
    },
  },
};
</script>

<style scoped>
.user-card {
  /* 添加样式 */
}
</style>

在上述示例中,UserCard组件具有高内聚性和低耦合性的特点:

  1. 高内聚:将所有与用户卡片相关的代码集中在一起。在模板中,我们使用插值表达式将用户的姓名和电子邮件显示在页面上。而发送消息和删除用户的功能都封装在sendMessagedeleteUser方法中。

  2. 低耦合:UserCard组件只依赖一个名为user的prop,该prop包含了需要展示的用户信息。这样,在其他地方使用UserCard组件时,只需要传递相应的user对象即可,而不需要关心组件内部的具体实现细节。

通过将相关的代码放在一起,并减少与其他组件的依赖,我们实现了高内聚低耦合的设计。这样可以提高组件的独立性、可维护性和可测试性。如果未来需要修改发送消息或删除用户的逻辑,我们只需关注sendMessagedeleteUser方法,而不会影响到其他部分的代码。同时,通过将UserCard组件解耦,我们可以更灵活地在不同的项目或页面中重用该组件。

  1. 可配置和可扩展(Configurable and Extensible): 业务组件应该提供足够的配置选项和接口,以适应不同的使用场景和需求。这样用户可以根据自己的需求进行定制化,并且能够方便地对组件进行扩展。

具体的示例:一个名为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: {
      typeArray,
      requiredtrue,
    },
    tabListClass: {
      typeString,
      default'default-tab-list'// 默认样式类名
    },
  },
  data() {
    return {
      activeTabIndex0,
    };
  },
  computed: {
    activeTabKey() {
      return this.tabs[this.activeTabIndex].key;
    },
  },
  methods: {
    changeTab(index) {
      this.activeTabIndex = index;
    },
  },
};
</script>

<style scoped>
.tabs {
  /* 添加样式 */
}
.default-tab-list {
  /* 默认选项卡样式 */
}
</style>

在上述示例中,Tabs组件允许用户通过提供tabstabListClass两个props来自定义选项卡的样式、布局和行为。

  • tabs prop 是一个包含选项卡信息的数组。每个选项卡对象应该具有一个label属性表示标签的显示文本,以及一个key属性表示标签的唯一标识。

  • tabListClass prop 是一个字符串类型,用于设置选项卡列表的样式类名。如果用户未提供该props,默认使用default-tab-list作为样式类名。

在模板中,我们使用v-for指令遍历tabs数组,为每个选项卡生成一个li元素。通过绑定样式类和点击事件,我们可以根据activeTabIndex来控制当前活动(选中)的选项卡。

通过使用插槽,我们可以将选项卡对应的内容显示在组件内部的.tab-content元素中。插槽的名称是根据当前活动选项卡的key动态生成的。

通过这种方式,用户可以自定义选项卡的样式类名、布局和行为。他们可以传递不同的tabs数组和tabListClass值,以满足不同项目的需求。这种可配置和可扩展的设计使得Tabs组件更加灵活和易于复用,在不同的场景下都能适应不同的样式和需求。

  1. 易于使用和理解(Easy to Use and Understand): 提供清晰的文档、示例和注释,以便其他开发人员能够轻松地理解和使用你的组件。组件的接口和命名应该简洁明了,使其易于上手和集成到项目中。

具体的示例:假设我们正在开发一个名为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 = {
        usernamethis.username,
        passwordthis.password,
      };
      // 提交用户凭据或执行其他操作
    },
  },
};
</script>

<style scoped>
.login-form {
  /* 添加样式 */
}
</style>

在上述示例中,LoginForm组件易于使用和理解,具有以下特点:

  1. 简洁明了:表单结构清晰简洁,只包含用户名、密码输入框和登录按钮。这使得用户可以快速理解组件的功能,并且不会感到困惑。

  2. 直观易懂:在模板中,我们使用v-model指令将输入框与组件的usernamepassword数据属性进行双向绑定。这样用户输入的内容会及时反映在组件内部的数据中。

  3. 易于交互:通过监听表单的submit事件,在提交表单时调用submitForm方法处理登录逻辑。用户只需要填写用户名和密码并点击登录按钮即可完成登录操作。

  4. 简单样式:为了保持简洁性,示例代码没有提供过多的样式。根据实际需求,可以添加适当的样式来增强用户界面的可视性和吸引力。

通过设计易于使用和理解的组件,用户可以快速上手和操作,减少学习成本和困惑。同时,简洁明了的代码结构和直观的用户界面也使开发人员能够轻松理解和维护组件,提高开发效率和代码质量。

  1. 灵活性和复用性(Flexibility and Reusability): 组件应该设计得足够灵活,以适应各种不同的业务需求。可以通过参数配置、插槽机制和事件触发等方式来增加组件的灵活性,并提高其复用性。

具体的示例:假设我们正在开发一个名为Notification的通知组件,该组件可以显示系统通知、成功消息或错误信息等。我们希望这个组件既可以作为全局的通知栏,也可以嵌入到其他组件中使用。

<template>
  <div class="notification" :class="notificationType">
    {{ message }}
  </div>
</template>

<script>
export default {
  props: {
    message: {
      typeString,
      requiredtrue,
    },
    type: {
      typeString,
      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组件具有灵活性和复用性的特点:

  1. 灵活配置:通过提供messagetype两个props,用户可以配置通知显示的文本内容和类型。type属性默认为info,并通过一个自定义的验证器确保用户提供的类型符合预期。这使得组件可以根据需求显示不同类型的通知,如信息、成功或错误。

  2. 可嵌入复用:由于使用了单文件组件的方式,Notification组件可以直接在其他组件中引用和嵌入,以满足特定页面或组件的通知需求。它可以被重复使用,同时又具备灵活配置的特点。

  3. 样式分离:通过添加带有特定类型前缀的样式类,我们为每个通知类型定义了相应的样式。这种样式分离的设计使得组件的外观易于修改和扩展,同时也遵循了单一责任原则。

通过这种设计,我们使Notification组件具有灵活性和复用性,可以在不同的场景中进行配置和使用。用户可以根据需要传递不同的messagetype值来定制通知的内容和样式。同时,通过将通知的显示和样式逻辑封装在组件内部,我们实现了高内聚低耦合的设计,提高了组件的可维护性和扩展性。

  1. 健壮性和容错性(Robustness and Fault-tolerance): 考虑到异常情况和错误处理,确保组件在面对不良输入或异常情况时能够正常运行,并提供友好的错误提示和处理机制。

具体的示例:一个表单输入组件要能够对用户的输入进行验证和处理,并提供合适的错误提示。无论用户输入是空值、非法字符还是格式错误,组件都应该能够正常运行,并提供友好的错误信息。

<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'',
      errorfalse,
      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组件具有健壮性和容错性的特点:

  1. 输入验证和处理:通过监听输入框的input事件,我们可以实时获取用户输入,并进行相应的验证和处理逻辑。在示例中,我们对输入值进行了两个简单的验证:空值和非法字符。根据验证结果,我们设置error属性来标记是否出现错误,并设置errorMessage属性来存储错误提示信息。

  2. 友好的错误提示:在模板中,我们使用条件渲染来显示错误提示信息。当errortrueerrorMessage不为空时,错误提示会显示在组件下方。这样用户可以清晰地看到输入的错误并得到相应的提示。

  3. 容错机制:无论用户输入是空值、非法字符还是格式错误,组件都能够正常运行并提供友好的错误信息。即使出现错误,用户仍然可以继续输入并进行验证,直到输入符合要求为止。

通过这种设计,我们实现了健壮性和容错性的表单输入组件。它可以对用户的输入进行验证和处理,同时提供合适的错误提示。这样可以增加用户体验和可靠性,防止因用户输入错误而导致整个应用程序崩溃或产生其他问题。

  1. 性能优化(Performance Optimization): 在设计组件时,考虑到性能问题,并避免不必要的重复计算、渲染或数据处理。优化代码结构和算法,以提高组件的性能和响应速度。

具体的示例:一个列表渲染的组件在处理大量数据时可能面临性能问题。通过虚拟滚动技术,只渲染可见区域的数据,避免不必要的渲染操作,从而提高组件的性能和响应速度。

<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: [], // 可见的数据项
      totalHeight0,
      viewportHeight0,
      itemHeight30// 每个数据项的高度
    };
  },
  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;
  top0;
  left0;
  right0;
  overflow: hidden;
}
</style>

在上述示例中,VirtualList组件利用虚拟滚动技术实现了只渲染可见区域的数据项:

  1. listContainer元素是包含整个列表的容器,通过监听其滚动事件来触发可视区域计算。
  2. calculateVisibleItems()方法中,根据容器的滚动位置和可视区域的高度计算出可见的数据项的起始和结束索引,从而确定需要渲染的数据范围。
  3. 在模板中,我们使用v-for指令遍历可见数据项,通过设置样式的transform属性来调整每个数据项的位置,实现滚动效果。
  4. 为了确保性能优化,我们将列表容器的滚动位置与每个数据项的高度进行对齐。这样可以避免不必要的重复渲染操作。

通过使用虚拟滚动技术,VirtualList组件在处理大量数据时只渲染可见区域的数据项,避免了渲染整个列表的性能问题。这样可以提高组件的性能和响应速度,使用户在滚动列表时能够获得更流畅的体验。

  1. 可测试性(Testability): 设计组件时应该考虑到测试的需求,并使组件易于进行单元测试和集成测试。模块化的设计和依赖注入等技术可以提高组件的可测试性。

具体的示例:一个名为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来处理日期选择和格式化:

  1. DatePicker类的构造函数接受一个dateService参数,这样可以将具体的日期服务实现注入到组件中。这种依赖注入的方式使得在测试时可以方便地替换日期服务实现为测试用例提供假数据或模拟行为。

  2. selectDate方法用于处理用户的选择,并将选择的日期存储在selectedDate属性中。而getFormattedDate方法使用了依赖的dateService来对选择的日期进行格式化。通过这种方式,我们将日期相关的逻辑解耦出来,使其易于测试并提高可维护性。

通过使用依赖注入和模块化设计,DatePicker组件具有良好的可测试性。我们可以编写针对不同情况的测试用例,验证组件在处理用户选择和返回日期数据时的正确性。同时,我们还可以轻松地替换日期服务的实现,以满足不同测试需求或模拟各种日期场景,从而完善测试覆盖范围。

最后

再给我们的辅导服务打个广告,我们目前有面试全流程辅导、简历指导、模拟面试、零基础辅导和付费咨询等增值服务,大厂前端专家一对一辅导。

辅导服务推出了近 2 年的时间,已助力超过 200 + 的同学找到心仪的工作,感兴趣的伙伴可以联系小助手(微信号:interview-fe2)了解详情哦~