vibe coding
术语课 前端篇:组件
术语课

前端篇:组件

AI 写的前端很丑,但是不知道怎么说怎么改?

一天一个 vibe coding 术语课,今天就讲讲组件。

一、什么是组件?

不论是电脑上的管理系统,还是手机里的购物页面,界面都可以拆成一些能单独组织、再组合使用的部分。这种独立的部分,可以是一个按钮、一个输入框,也可以是一张展示订单信息的卡片。

把这样一部分的结构、外观和相关行为组织在一起,让页面能够使用它,就形成了界面组件,英文 component。

比如,在手机上查看订单时,你会看到一张张订单卡片。每张卡片里有商品、金额、订单状态,还可能有取消订单、查看物流或申请售后等操作入口。

它里面可以继续使用图片、文字和按钮组件;整个订单列表又可以组合多张订单卡片。组件可以这样一层层组合。

把这些部分组合成订单卡片时,还要根据这笔订单的状态,决定显示哪些操作入口。

比如,待付款订单允许取消,已发货订单提供物流入口,已完成订单只有符合售后条件时才提供申请入口。同样是订单卡片,订单发生变化,显示的内容和可进行的操作也要跟着变。

把订单信息的展示和这些操作入口组织在同一个组件里,就是在做业务组件。它的职责已经具体到了「让用户查看并操作一笔订单」。

把组件内部的显示和操作处理好,再明确页面使用它时需要提供什么、能让它做什么,这个过程就叫封装,英文 encapsulation。使用订单组件的页面,就可以沿用它已经整理好的做法。

别的页面也需要展示订单时,就可以再次使用这个组件。

二、复用的价值,是同一套业务做法不用维护好几遍

假设同一个商城项目里,「我的订单」和「搜索订单的结果」都需要展示并操作订单,两处就可以使用同一个订单组件。这叫复用,英文 reuse。

两处展示的订单可以不同,外层列表的筛选方式也可以不同,但订单内部的展示规则和操作反馈共用同一份做法。

如果两处各做一份,后来新增一种订单状态,或者调整售后入口的展示规则,就得分别修改。漏改一处,用户在一个页面能看见入口,换个页面却找不到。

同一套规则写成多份,还要分别维护,这种不必要的重复,就是常见的代码冗余。

共用一个组件,也更容易统一设计风格:订单状态放在哪里、金额怎样突出、操作时的反馈怎样显示,都可以遵循同一套规则。以后调整这些共同样式,使用它的页面就能一起更新。

共用一套规则,也能减少重复修错的工作。比如某种订单已经不能申请售后,组件却仍显示入口;如果问题在共同的展示规则里,修正并验证这一处,两种列表都能受益。

但共同规则写错,也会影响所有使用它的地方。

共用一个组件,也可以让它在不同页面上显示成不同的样式。比如电脑上的文件选择界面,同一份文件展示组件,可以支持紧凑列表和缩略图卡片两种样式。列表把文件名、类型和大小横向排列,卡片突出预览图;它们仍然展示同一个文件,并沿用相同的选中和打开行为。

只要组件支持这两种布局,并让使用它的页面能够选择,就不必在每个页面里重新做一套文件展示和操作功能。这就是一套代码支持多套视觉。

需要一种新的显示样式时,先看现有组件能不能满足。已有的布局选项够用,就直接选择;需要的布局还不支持,再调整组件,加入这种布局。

组件的展示和操作规则可以共用,各处显示的数据和正在进行的操作仍可以不同。订单列表里的每张卡片要知道自己对应哪笔订单,文件组件要知道这次采用哪种布局,这些差异需要在使用时告诉它。

三、组件接收什么,又把什么交出去?

订单组件需要的订单信息、文件组件需要的列表或卡片样式,都可以由使用它的页面传进来。这类从外部传入的信息,叫组件属性,英文通常写成 props。组件支持哪些属性、收到以后怎样显示,需要在做组件时明确。

以订单组件为例,页面交给它订单信息和当前允许的操作,它就据此显示商品、金额、订单状态及相应入口。同一个组件收到不同订单,便能显示各自的内容;收到最新状态,也能更新对应入口。

有些信息则产生于用户操作。用户点击取消订单时,组件需要把「用户想取消哪笔订单」通知出去,让负责处理订单的功能接着执行。这种通知可以通过组件事件发出去,英文 event。

这个例子可以这样分工:组件负责展示订单和发出操作意图,页面发起取消请求,再把处理进度和结果传回组件。组件收到进度,就显示「处理中」;收到结果,就更新订单状态,或者显示失败原因。

用户点击取消,只是表达了取消订单的意图,还不能当作取消成功。真正处理订单的服务端程序,也就是运行在服务器上的业务程序,还需要按当前用户的权限和最新订单情况判断能否取消;组件依据返回结果反馈给用户。

每张卡片的处理状态还要对应各自的订单。取消其中一笔,不应该让其他订单都显示取消中;如果同一笔订单出现在不同页面,它的最新信息则需要由负责订单数据的部分保持一致。

这样,封装的边界就具体了:组件接收什么,负责哪段展示与交互,又把什么动作交给外部处理。使用它的地方按这个约定配合,就不用重复处理组件内部的细节。

那准备增加一块界面功能时,怎样判断它值不值得单独封装呢?

四、添加界面功能时,先考虑是否需要封装

对于组件,你需要的是封装的意识。往页面上添加任何一个模块、功能,都要优先考虑是否需要封装。

在同一个前端项目里,一块相似的功能区域反复出现,到了第三处,就值得考虑把共同部分封装成组件。

比如,「我的订单」和搜索结果里已经有了订单卡片,订单详情页又需要展示同样的订单摘要和操作入口。如果三处各做一份,以后改这些共同规则,就得分别修改。

这时可以把三处放在一起看:展示的是不是同一类信息,操作是不是同一套做法。如果只是订单数据、当前状态不同,就可以由页面通过属性传进来,共用订单组件。

但如果只是看起来像,实际做的事情不同,就不必硬合。比如订单卡片和商品卡片都有图片、名称和价格,但一个处理已有订单,另一个让用户挑选商品。图片、价格这些小组件可以共用,整张卡片的业务功能则各自组织。

如果你拿不准,每写完一个页面,就可以直接问 AI:「结合当前项目的业务需求,这个页面的哪些组件需要封装?」封装的活,直接交给 AI 即可。

确定要封装哪块功能以后,还要把它的交互过程考虑完整。比如优惠券选择,打开面板、试选、取消和确认,就需要放在一起考虑。

五、封装一段交互,要把开始、取消和确认都考虑进去

假设你封装了一个优惠券弹窗组件。用户在手机结算页已经选了一张优惠券,又打开选择面板,想看看换另一张能不能更划算。

面板打开时,要显示当前已经确认的选择。用户在里面点了另一张券,可以先看到新的预计优惠;但只要还没确认,这次勾选就只是临时选择。

如果用户点取消,或者按这个产品约定关闭面板,应当放弃这次临时选择,结算页仍保留原来那张券。再次打开时,也应该从当前已确认的选择开始。

如果面板里一勾选,外面的结算金额就被当成正式结果改掉,取消时又没有恢复,用户明明取消了,结算页却换成了另一张券。要避免这种情况,就得把临时选择与已确认选择分清。

这个例子里,可以让优惠券弹窗组件管理本次打开期间的临时勾选,结算页管理已确认的选择和金额展示。组件接收可选优惠券、当前选择和相关优惠信息,确认时交出用户想使用的那张券。

用户点确认后,组件把选择交给结算功能处理,并显示正在处理的状态。处理失败时保留这次选择、说明原因,让用户能重新选或取消;处理成功后,结算页再更新已确认的优惠券和对应金额。这张券能不能用、能优惠多少,由结算功能核验,优惠券弹窗组件负责把这个过程呈现清楚。

同一个优惠券弹窗组件用于另一个结算页面时,也能沿用这套打开、试选、取消和确认的行为。使用它的页面提供自己的可选券与当前结果,组件每次打开,都按这些信息显示当前选择,不把上一次没确认的临时选择带进来。

把选择、预览、取消、确认和失败处理组织起来,封装的就是一段完整的业务交互。

六、组件怎样组合不同的内容?

我们再来看这个优惠券弹窗组件,其实承载它的弹窗也是可以复用的部分。把这个组件拆开看,外层是弹窗,里面是选择优惠券的内容。比如商城里选择优惠券、修改收货地址,都可能使用弹窗。在这类弹窗中遮罩、标题栏和关闭按钮可以共用,中间则分别放优惠券列表和地址表单。

既然变的都是中间内容的位置,那么我们做通用弹窗组件时,就可以将中间的位置留出来,让用这个组件的业务自己来规定中间放什么。

比如:打开选券弹窗时,放入优惠券选择组件;打开地址弹窗时,放入地址表单组件。在一些前端框架里,这种内容组合方式叫插槽,英文 slot。

这样,弹窗负责共同的显示和关闭行为,里面的内容各自处理自己的交互。这种组合方式,让共同结构保留下来,里面的内容则可以按需要替换。

判断一块业务值不值得封装,最终要说得清:它负责什么,需要别人给什么,做完以后交出什么。共同的规则集中维护,各自的变化有明确去处,组件才真正帮项目减少了混乱。

该内容为@openbeetles原创内容,未经授权,禁止转载,违者必究!