两年前接手过一个后台管理系统,那简直是一场灾难。
当时有个需求很简单:把所有页面的“导出Excel”按钮颜色从蓝色改成绿色。结果,团队里的两个小伙伴整整改了一天半。为什么?因为这个按钮在40多个页面里是直接复制粘贴的代码,而且有的页面写死在HTML里,有的封装了一半,有的甚至跟具体的业务接口绑死在一起。
那时我才深刻意识到:很多中小团队所谓的“组件化”,其实只是“把代码挪个位置”而已。
如果你觉得组件化就是为了“复用”,那你大概率还在踩坑。对于中小团队,组件化的核心价值只有两个:降低认知负荷和隔离变更风险。
今天分享我在一线“填坑”多年总结的拆解思路,不整虚头巴脑的理论,只聊怎么落地。
一、 警惕“上帝组件”:别把配置项当万能药
这是新手最容易犯的错,也是我每周五代码Review时“杀”得最狠的一类代码。
观点: 组件的灵活性不是靠无限增加 Props(属性)来实现的,而是靠组合。
真实案例:
去年我们组招了个挺机灵的小伙子,叫小张。他为了体现“封装能力”,搞了一个通用的 ListTable 组件。起初挺好用,只需传个数据数组进去。
两周后,噩梦开始了:
- 需求A:有的列要加红,小张加了
renderRed属性; - 需求B:表头要支持点击排序,小张加了
sortable和onSort; - 需求C:第一列要加复选框,但有时候是单选,他又加了
selectionType…
三个月后,这个 ListTable 变成了拥有 30多个 Props、内部充满 if-else 判断的“上帝组件”。任何人都没法维护,因为改一个属性,可能会炸掉其他五个页面的展示。
硬核解法:控制反转(Inversion of Control)
别试图在组件内部预判所有场景。把“怎么展示”的权利交还给调用者。
思考题: 你的项目里有没有那种超过200行代码、Props 超过10个的基础组件?如果有,它就是那个随时会爆的雷。
代码对比:
❌ 错误的上帝模式:
// 这种组件写出来就是为了折磨队友的
<ListTable
data={list}
showCheckbox={true}
checkboxType="radio"
enableSort={true}
headerColor="blue"
// ...后面还有20个属性
/>
✅ 推荐的组合模式(Slot/Children):
// 基础组件只管布局和样式,内容由外部决定
<Table>
<TableHead>
<Checkbox />
<SortIcon />
</TableHead>
<TableBody>
{list.map(item => (
<Row>
<Cell>{item.name}</Cell>
{/* 特殊逻辑直接在这里写,不用侵入组件内部 */}
<Cell style=>
{item.status}
</Cell>
</Row>
))}
</TableBody>
</Table>
当你发现一个组件需要传 styleObject 或者 className 进去覆盖默认样式时,通常说明拆解粒度不够,或者封装得太死了。
二、 拒绝“业务入侵”:让UI组件变“哑巴”
这是造成项目难以维护的头号杀手。很多项目经理抱怨:“为什么换个接口,前端要改两天?”多半是因为业务逻辑渗透到了UI组件里。
观点: UI组件应该是“哑巴”,只负责“画图”和“喊话”(触发事件),绝对不能知道数据是从哪里来的。
真实案例:
我们曾经有个“用户卡片”组件。最开始是在CRM系统里用的,代码里直接引入了 fetchUserDetail(id) 这个API请求。
后来做营销活动页,产品经理想复用这个卡片样式,展示中奖用户信息。结果发现复用不了——因为营销活动的数据结构跟CRM完全不一样,而且不需要调那个API。
结果就是:前端不得不Ctrl+C、Ctrl+V,复制了一份代码改名叫 WinnerCard。随着版本迭代,两份代码样式逐渐不一致,UI走查天天报Bug。
硬核解法:容器组件 vs 展示组件
这个方法我用了3年,百试百灵。强制要求团队把组件分为两类:
-
展示组件(Dumb Component):
- 只收数据(Props),不调接口。
- 不知道业务逻辑,只负责长得好看。
- 放在
components/ui目录下。
-
容器组件(Smart Component):
- 负责调接口、处理数据转换。
- 把处理好的纯数据传给展示组件。
- 放在
components/features或pages目录下。
实操步骤:
当你写代码时,如果发现在一个通用按钮里引入了 store 或者 axios,请立刻停手。你应该在外面包一层,把点击事件抛出来。
“展示组件就像个没头脑的画师,容器组件才是那个操心的管家。”
三、 别做“预言家”:遵循“三次法则”
很多有追求的开发人员(包括曾经的我)都有洁癖,写第一遍代码时就想把它封装得完美无缺,支持未来可能出现的各种需求。
观点: 过早优化是万恶之源。在重复出现第三次之前,允许复制粘贴。
真实案例:
去年做个活动页,有个倒计时功能。有个兄弟觉得以后肯定常用,于是花了半天时间封装了一个巨牛逼的 CountDown:支持毫秒级、支持服务器时间校准、支持自定义格式化字符串、支持倒计时结束回调…
结果呢?那个活动上线3天就下线了。那个巨复杂的倒计时组件再也没人用过,还留在那占体积。反而是后来另一个活动需要简单的“天:时:分”,大家嫌那个组件太重,又重写了一个。
硬核解法:Rule of Three(三次法则)
- 第一次: 只管写业务,代码直接写在页面文件里,怎么快怎么来。
- 第二次: 当遇到相似场景,允许复制粘贴(Copy-Paste),稍微改改。
- 第三次: 当你发现自己在复制第三次时,这时候你已经非常清楚共性是什么、差异是什么了。此刻,才是重构和提取组件的最佳时机。
不要为了“可能”的需求去写代码,只为“当前”的痛点去封装。
总结与行动
组件化拆解不是为了炫技,而是为了让队友少骂你两句,为了下班能准时走。
回顾一下核心思路:
- 别搞上帝组件:用组合替代配置,属性越少越好。
- 别让业务入侵:UI归UI,数据归数据,把API请求从UI组件里踢出去。
- 别做预言家:忍住封装的冲动,直到代码重复了三次。
给你的落地建议(Action Plan):
- 本周自查: 打开你的项目,找到那个Props最多的组件,尝试用“组合模式”重写一个简单的Demo,对比一下灵活性。
- 清理逻辑: 检查公共组件库,凡是里面包含
axios、fetch或者特定业务状态判断的,标记出来,下次迭代计划拆分。 - 制定红线: 在团队里定个死规矩——通用UI组件禁止包含任何业务接口调用。
最后想问问你: 在你现在的项目里,修改一个通用的顶部导航栏,需要改动几个文件?如果超过1个,或许你该试试上面的方法了。