写代码就像盖房子 —— 前端组件化探索

443 阅读8分钟

你们有没有盖过房子?

如果把写代码比作盖房子的话,你觉得应该怎么做,才能把房子盖得又结实又漂亮呢?

暂停阅读思考三分钟...

2d95958b-1213-48b0-9eaf-06347b08d00c.jpg

整洁有序,一目了然

大家都知道,盖房子首先要有一个整体的设计图纸,把房子的布局、每个房间的用途等都规划好。要是东一只手西一只手,随意盖哪儿算哪儿,那最后只会得到一个乱七八糟的怪胎建筑。

同样,写代码也需要先有一个整体设计,不能东拼西凑,乱哩糊涂。怎么设计呢?咱们可以遵循一个"整洁架构"的理念。所谓"整洁架构"嘛,就是把代码按照不同的职责分成几层,每一层只关注自己的那一块事儿,不去操心其他层的事。

比如,假设我们要做一个电商网站,可以把代码分成这几层:

  • 表现层:就是我们看到的网页界面,负责显示数据和处理用户交互
  • 业务逻辑层:实现具体的业务逻辑,比如加入购物车、下单支付等
  • 数据访问层:负责从数据库或接口获取数据
  • 基础设施层:提供一些基础的工具功能,比如日志、缓存等

每一层只关注自己的工作,不去操心其他层的事情。表现层只管显示数据就行,具体这些数据从哪儿来的,它不用care。业务逻辑层只管实现具体的业务规则,至于用户界面长啥样,它也不用care。大家各司其职,井井有条,这不就像盖房子的时候,工人们分工协作,木工、电工、水暖工各干各的一样。

模块化,一卡一卡往外搭

现在,咱们把注意力集中到表现层,也就是网页界面这块。一个网页界面通常都是由很多小模块组成的,比如头部导航、侧边栏、内容区等等。而把每个小模块都单独拿出来,就形成了所谓的"模块化"开发方式。

模块化的好处就是,每个模块都是一个独立的、可复用的单元,负责实现一个特定的功能。以后无论在哪个地方,只要需要这个功能,就直接拿过来用就是了,省事又省力。

就好像盖房子的时候,已经提前做好了门、窗、柜子这些部件一样。需要用的时候,直接把成品部件拿来安装就行,不用现场临时焊焊割割,费力不讨好。

而且,模块与模块之间是相互隔离的,改动一个模块不会影响到其他模块。这就像房子里的卫生间和厨房是分开的,你在厨房renovate,卫生间的设计自然不会受影响一样。这种"低耦合"的设计,可以最大限度地减少代码的牵连关系,减少维护成本。

<!-- 一个简单的模块化的导航栏组件 -->
<template>
  <nav>
    <ul>
      <li><a href="#">首页</a></li>
      <li><a href="#">产品</a></li>
      <li><a href="#">服务</a></li>
      <li><a href="#">关于</a></li>
    </ul>
  </nav>
</template>

<script>
export default {
  name: 'NavBar'
}
</script>

<style scoped>
  /* 导航栏的样式 */
</style>

组件化,把模块挺拢起来

可是,如果每个模块都是独立的,它们是怎么被组装到网页上的呢?这就需要用到"组件化"的概念了。

所谓组件,就是把若干个模块合拢到一起,形成一个更大的功能单元。比如,头部导航、侧边栏、内容区,它们就可以被合并成一个页面组件。

组件化的好处是,让整个网页变成了一个类似于乐高积木的东西。每个小组件就是一块积木,我们可以随意把这些积木组合到一起,拼出各种各样的页面。

而且,每个组件内部还可以继续嵌套其他更小的组件,就像一个大的乐高积木由无数个小积木拼起来一样。这种设计,可以让我们实现页面元素的无限嵌套和复用。

<!-- 一个页面组件,由多个模块组合而成 -->
<template>
  <div>
    <NavBar />
    <SideBar />
    <MainContent />
    <Footer />
  </div>
</template>

<script setup>
import NavBar from './NavBar.vue'
import SideBar from './SideBar.vue'
import MainContent from './MainContent.vue'
import Footer from './Footer.vue'
</script>

所以,如果把写代码比作盖房子的话,模块就相当于房子的一个个部件,而组件则相当于把这些部件组装到一起,形成一个更大的功能单元。由小到大,层层递进,最终就构建出了整个房子(也就是网页)的框架。

高内聚,人人都有自己的活

不过,光有框架还不够,咱们还得确保每个模块和组件都被设计得很"高内聚"。

啥叫高内聚呢?就是说,一个模块或组件,它只负责实现一个特定的功能,而且要把这个功能实现得很好。就像一个厨师,他只管烧菜,而且要把每一道菜都做到极致一样。他才不会去想,洗菜的活儿该不该自己干,锅碗瓢盆该不该自己刷呢?

比如说,有一个购物车模块,它只需要关注购物车相关的功能就行了,例如:

  • 加入购物车
  • 从购物车删除
  • 修改购物车内商品数量
  • 计算总价格

至于从哪里拿数据、最终要把订单提交给谁,都不归购物车模块管。它只管把自己的那点事儿做好就行了。做到这种"高内聚",可以大大降低模块的复杂度,保证其可维护性和可复用性。

低耦合,各家自扫门前雪

除了要"高内聚"之外,模块与模块之间、组件与组件之间,它们的关系还要"低耦合"。

所谓低耦合,就是说它们之间的依赖要尽量减少。就像一家人和邻居们之间,虽然偶尔会相互照应,但日常生活还是要各自独立,各家自扫门前雪。谁也不应该老是过问别人家里的事儿,对吧?

在代码里表现出来,就是要最大限度地减少模块或组件之间的相互引用。如果实在需要引用的话,那就通过一些标准化的接口来获取数据和调用方法,而不是直接操作对方的内部实现。

这样一来,每个模块和组件都可以对外界"黑盒化",不需要知道其他模块的内部细节是怎样的,自己修改自己的内部实现也不会影响到其他模块。你可以想象,如果每个房间的设计都牵扯到了其他房间,那如何改动就会变得无比困难了。

<!-- 两个低耦合的组件 -->
<template>
  <div>
    <ProductList @add-to-cart="handleAddToCart" />
    <ShoppingCart :cart-items="cartItems" />
  </div>
</template>

<script>
import ProductList from './ProductList.vue'
import ShoppingCart from './ShoppingCart.vue'

export default {
  components: {
    ProductList,
    ShoppingCart
  },
  data() {
    return {
      cartItems: []
    }
  },
  methods: {
    handleAddToCart(product) {
      this.cartItems.push(product)
    }
  }
}
</script>

在上面的示例中,ProductList 和 ShoppingCart 两个组件是相互隔离的。ProductList 只负责显示商品列表,当用户点击"加入购物车"按钮时,它会发出一个事件,而不会直接操作购物车的内部数据。ShoppingCart 组件则只负责展示购物车中的商品,并不关心这些商品数据从何而来。两个组件通过标准化的接口(props 和事件)进行数据传递,而不是直接耦合在一起,从而实现了"低耦合"的设计。

模块化 + 组件化 = ?

最后再啰嗦几句,模块化和组件化,它们是两个相辅相成的概念。

模块化是从底层来实现代码的复用,把每个小功能都做成一个可复用的模块。而组件化则是从上层来实现复用,把一些常用的页面结构做成可复用的组件,每个组件内部又由许多子模块构成。

两者结合起来,就形成了现代化的前端开发模式:

整个项目被分解成无数的小模块,每个模块只负责实现一个特定的功能;而这些小模块又被有机地组合到一起,形成各种各样的组件;不同组件再组合到一起,就构建出了完整的页面。

每个小模块的职责都很单一、内聚度很高,但它们之间又是相互隔离、低耦合的,构成了一个松耦合的系统。从理论上来说,如果做到了真正的"高内聚、低耦合",整个系统就会变得极其灵活和可扩展,也更容易维护和协作开发。

就好像盖房子一样,虽然每个部件功能都很简单,但把它们合理地拼装在一起,就能建出别墅大厦,还是挺有意思的吧?

所以,各位,以后要为自己的代码设计一个合理的框架结构哦,别老是把所有东西胡乱堆在一起,那就失去了模块化和组件化的意义了。代码要设计,想好了再开始写。代码质量提高了,同事羡慕,leader欣赏,boss加薪。。。