我们经常从一个按钮开始,最终却在维护一整套系统。真正困难的不是写出第一个组件,而是在需求不断变化时,让代码依然容易理解。
这篇笔记记录一种渐进的方法:先看清变化,再划定边界,最后才考虑抽象。
先找到变化的方向
拆分组件之前,先问三个问题:
- 哪些内容会随着业务需求一起变化?
- 哪些行为在不同页面中保持一致?
- 使用这个组件的人,需要知道多少内部细节?
一个商品卡片和一篇文章摘要都可能有标题、图片和描述,但它们的业务行为并不相同。相似的外观不一定意味着相同的抽象。
把经常一起变化的东西放在一起,把因不同原因变化的东西分开。
让接口表达意图
一个清晰的组件接口应该告诉调用者「它能做什么」。与其传入十几个布尔值,不如用一个明确的状态描述当前情景。
type SaveState = 'idle' | 'saving' | 'saved'
function SaveButton({ state }: { state: SaveState }) {
const labels = {
idle: '保存文章',
saving: '正在保存…',
saved: '已保存',
}
return (
<button disabled={state !== 'idle'}>
{labels[state]}
</button>
)
}互斥的状态可以减少无效组合。调用者不必再猜测:同时传入 loading 和 success 时,到底应该显示什么?
从组合开始
如果一段 UI 的布局稳定,而内容变化很大,可以先用 children 或命名插槽来组合。这样既保留了边界,也不会提前把每一种业务情况塞进底层组件。
区分三个层次
| 层次 | 关注的问题 | 示例 |
|---|---|---|
| 基础组件 | 交互与可访问性 | Button、Dialog |
| 业务组件 | 业务语义与展示 | ArticleCard、AuthorBio |
| 页面 | 数据与组合 | 首页、文章详情 |
这不是必须遵守的目录模板,而是一种沟通工具。小项目可以保持扁平,只有在真实的复杂度出现时,才增加结构。
为下一次修改留出空间
每次提交前,我会检查:命名是否足够清晰,数据是否靠近使用位置,重复代码是否已经表现出稳定的共同模式。
架构不必预测所有未来。它只需要让下一次合理的修改,不必推倒整个项目。先让代码诚实地表达当前需求,再在变化中逐步找到秩序。
感谢你读到这里。
思考没有终点,下一篇见。