Why Your Reusable Components Keep Breaking (And How to Fix Your API Design)
Why Your Reusable Components Keep Breaking (And How to Fix Your API Design)
为什么你的可复用组件总是“坏掉”(以及如何优化你的 API 设计)
Ever stared at a component library you built just three weeks ago, only to realize it’s already suffocating under a mountain of boolean props like hasBadge, isCompact, and withIcon? I ran into this exact wall recently while refactoring a set of modular landing page cards for a mixed-media client project.
你是否曾盯着三周前构建的组件库,却发现它已经被堆积如山的布尔属性(如 hasBadge、isCompact 和 withIcon)压得喘不过气来?最近我在为一个多媒体客户项目重构一组模块化落地页卡片时,就遇到了这个棘手的问题。
What started as a clean, reusable UI module quickly devolved into a brittle spaghetti monster the moment a new layout requirement dropped. Every time a client needed a tiny structural tweak—like shifting an image from top to side, or adding a secondary action tag—I found myself cracking open the core component file and risking regressions across the entire layout. 原本简洁、可复用的 UI 模块,在接到新的布局需求后,迅速退化成了一个脆弱的“意大利面条式”代码怪物。每当客户需要进行微小的结构调整——比如将图片从顶部移到侧边,或者添加一个次要操作标签——我就不得不打开核心组件文件进行修改,并冒着导致整个布局出现回归错误的风险。
The underlying problem isn’t just poor planning; it’s treating components like rigid black boxes instead of flexible composition primitives. Here is what that trap looks like in code: 根本问题不仅仅是规划不当,而是将组件视为僵化的“黑盒”,而不是灵活的组合原语。以下是这种陷阱在代码中的表现:
// The Trap: A monolithic component buckling under conditional props
// 陷阱:在条件属性下不堪重负的单体组件
function ProductCard({ title, price, badgeText, isLarge, hasImage, imageSrc, variant }) {
return (
<div className={`card ${variant} ${isLarge ? 'large' : ''}`}>
{hasImage && <img src={imageSrc} alt={title} />}
{badgeText && <span className="badge">{badgeText}</span>}
<h3>{title}</h3>
<p>{price}</p>
</div>
);
}
To break out of this cycle, I had to shift away from monolithic prop drilling and lean into compound component patterns—handing structural control back to the consumer while keeping styles neatly encapsulated: 为了打破这种循环,我不得不放弃单体组件的属性透传(prop drilling),转而采用复合组件模式(compound component patterns)——将结构控制权交还给使用者,同时保持样式的整洁封装:
// The Fix: Composable layout primitives
// 解决方案:可组合的布局原语
function Card({ children, className }) {
return <div className={`card-base ${className || ''}`}>{children}</div>;
}
Card.Header = function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
};
Card.Body = function CardBody({ children }) {
return <div className="card-body">{children}</div>;
};
// Usage: Clean, extensible, and untouched core logic
// 使用方式:简洁、可扩展,且无需触动核心逻辑
export default function App() {
return (
<Card>
<Card.Header>
<span className="badge">Featured</span>
<img src="/assets/preview.svg" alt="Preview" />
</Card.Header>
<Card.Body>
<h3>Dynamic System Spec</h3>
<p>Structured layout tokens in motion.</p>
</Card.Body>
</Card>
);
}
My question: How do you usually handle this in your own codebases? Do you enforce strict, heavily-propped components to keep teams locked into a rigid design system, or have you shifted toward compound composition patterns to handle custom layout variations? How do you keep things maintainable? 我的问题是:你在自己的代码库中通常是如何处理这种情况的?你是强制使用严格、属性繁多的组件来确保团队遵循僵化的设计系统,还是已经转向复合组件模式来处理自定义的布局变化?你是如何保持代码的可维护性的?