做组件库最容易踩的坑,是把组件设计得「太懂业务」。
我们的 YBList 最早是数据模型驱动的:定义一个 YBListItemModel,里面塞满了 title、subtitle、rightText、showArrow、switchOn……组件内部根据这些字段决定渲染什么。
一开始很爽,业务方三行代码就能出一个设置页。
然后就崩了
第一个需求来了:右侧要放一个自定义的角标。加个 badgeText 字段。
第二个需求:角标要能带图片。加 badgeImage。
第三个:右侧是个下拉选择器,展开后有动画。
到这里 model 已经有 14 个字段,组件内部是一坨 if-else,而且每加一个字段,所有已接入的业务都要重新回归一遍。组件成了瓶颈,而不是加速器。
换个思路
问题的根源在于:组件试图穷举业务的所有可能性。而业务的可能性是无穷的。
所以换成容器壳(slot)驱动 —— 组件只负责三件事:
- 定义布局骨架(左侧标题区、右侧内容区)
- 管理约束关系和间距规则
- 提供一个空的容器,业务爱塞什么塞什么
// Before:组件决定渲染什么
model.rightText = @"已开启";
model.showArrow = YES;
[cell configWithModel:model];
// After:业务决定渲染什么,组件只管布局
cell.titleText = @"消息通知";
cell.subtitleText = @"接收新消息提醒";
[cell setRightContentView:myCustomView]; // 想放啥放啥
组件代码量少了 40%,if-else 全没了。
布局才是真正要解决的问题
放开内容之后,唯一需要严格约束的反而是布局规则。我们的规定很简单:
- 标题/副标题与右侧内容之间,最小物理间距 ≥ 32px
- 右侧内容的抗压缩优先级更高(业务塞的东西优先保证完整显示)
- 左侧文字在空间不足时省略号截断
[self.titleLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[self.rightContainer setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
就这三条规则,覆盖了目前所有业务场景,包括那些当初逼我加字段的需求。
一点体会
好的组件 API 不是「提供更多能力」,而是「明确更少的职责」。
数据驱动的问题不在于数据,而在于组件把「渲染决策」也接了过来。而渲染决策天然属于业务。组件应该只做业务做不了、或者每个业务都要重复做的那部分 —— 在这个例子里,就是布局约束。
顺带一个收获:字段变少之后,动态字体(我们支持 5 级缩放)的适配成本也降了,因为不再需要为每种 if-else 分支单独验证一遍布局。