一个Title组件里塞了3种按钮逻辑,类型用字符串枚举控制。这种写法在2023年的代码评审里依然能过,但产品上线后第4个需求进来时,开发者开始骂娘。
这不是技术债的典型案例,是设计模式课没讲透的后遗症。开闭原则(OCP,Open Closed Principle)说"对扩展开放,对修改封闭",但很多人直到被需求淹没才懂这句话的代价。
一个Props对象埋下的雷
原文里的Title组件接收5个Props:title、type、href、buttonText、onClick。type字段用联合类型锁死三种形态:"default" | "withLinkButton" | "withNormalButton"。
渲染逻辑用条件分支硬编码:type匹配字符串,决定要不要渲染按钮、渲染哪种按钮。这种写法在需求稳定时人畜无害,甚至显得"简洁"。
问题藏在"修改"两个字里。
产品经理说标题旁边要加tooltip,开发者得做三件事:改Props类型定义、加"withTooltip"分支、测试前三种形态有没有被改坏。每次扩展都要碰核心文件,回归测试成本线性增长。
更隐蔽的坑是type字段的语义污染。它描述的是"有没有按钮、什么按钮",但新需求可能是"按钮旁边加图标"或"标题带副标题"。字符串枚举的扩展性在第三个需求后就捉襟见肘。
组合模式怎么解耦
重构后的方案把Title拆成容器组件和具体实现。基础Title只负责布局和标题渲染,children作为插槽暴露出去。
Link按钮版本、普通按钮版本各自封装成独立组件,内部组装基础Title。新需求来了?新建一个TitleWithTooltip组件,完全不碰现有代码。
扩展成本从"改核心+全量回归"变成"写新文件+单测新组件"。
这种模式在React生态里有成熟实践。Ant Design的Modal、Form,Radix UI的Dialog,都是容器+插槽的思路。区别只在于有没有把"不要改旧代码"当成铁律。
原文没提但值得注意:组合模式在TypeScript里需要处理好children的类型约束。基础Title的children用ReactNode太宽,用特定组件类型又太死,实际项目中往往要配合render props或context做类型收窄。
为什么团队明知故犯
第一种写法在代码量上确实少。5个Props、3个条件分支,200行内搞定。组合模式需要3个文件、更多样板代码,评审时容易被质疑"过度设计"。
但代码量的比较有个陷阱:它只算第一次写的成本,不算后面修改的成本。原文的场景里,第二次加tooltip时,第一种方案的总代码量(含测试)就已经反超。
另一个阻力是团队习惯。很多前端从jQuery时代过来,习惯了"拿到需求改组件"的肌肉记忆。React官方文档早期推崇的"一切皆组件"口号,被误读成"把所有东西塞进一个组件"。
2019年React Hooks发布后,函数组件成为主流,组合模式的实践才逐渐普及。但历史包袱还在——你能在2024年的生产代码里找到大量class组件时代的"万能组件"遗迹。
开闭原则的边界在哪
严格遵循OCP需要预判变化方向。如果产品经理明确说"标题区域永远只有这三种按钮",第一种写法就是合理的技术债。问题在于这种承诺在敏捷开发里几乎不可信。
原文的解决方案也有成本。组件数量膨胀后,目录结构需要设计。Title、TitleWithLink、TitleWithButton、TitleWithTooltip散落在各处,还是按功能聚合到title/目录下?这关系到代码可发现性。
更现实的折中是"预留扩展点但不提前实现"。基础Title保留children,但先只实现一种具体形态。需求来了再扩展,比推倒重来便宜得多。
你的团队现在怎么处理这类场景?是坚持组合模式,还是用条件分支硬扛?最近一次因为"加个type"导致的线上事故是什么时候?
热门跟贴