我们经常从一个按钮开始,最终却在维护一整套系统。真正困难的不是写出第一个组件,而是在需求不断变化时,让代码依然容易理解。

这篇笔记记录一种渐进的方法:先看清变化,再划定边界,最后才考虑抽象。

先找到变化的方向

拆分组件之前,先问三个问题:

  1. 哪些内容会随着业务需求一起变化?
  2. 哪些行为在不同页面中保持一致?
  3. 使用这个组件的人,需要知道多少内部细节?

一个商品卡片和一篇文章摘要都可能有标题、图片和描述,但它们的业务行为并不相同。相似的外观不一定意味着相同的抽象。

把经常一起变化的东西放在一起,把因不同原因变化的东西分开。

让接口表达意图

一个清晰的组件接口应该告诉调用者「它能做什么」。与其传入十几个布尔值,不如用一个明确的状态描述当前情景。

components/save-button.tsx
type SaveState = 'idle' | 'saving' | 'saved'
 
function SaveButton({ state }: { state: SaveState }) {
  const labels = {
    idle: '保存文章',
    saving: '正在保存…',
    saved: '已保存',
  }
 
  return (
    <button disabled={state !== 'idle'}>
      {labels[state]}
    </button>
  )
}

互斥的状态可以减少无效组合。调用者不必再猜测:同时传入 loadingsuccess 时,到底应该显示什么?

从组合开始

如果一段 UI 的布局稳定,而内容变化很大,可以先用 children 或命名插槽来组合。这样既保留了边界,也不会提前把每一种业务情况塞进底层组件。

区分三个层次

层次关注的问题示例
基础组件交互与可访问性Button、Dialog
业务组件业务语义与展示ArticleCard、AuthorBio
页面数据与组合首页、文章详情

这不是必须遵守的目录模板,而是一种沟通工具。小项目可以保持扁平,只有在真实的复杂度出现时,才增加结构。

为下一次修改留出空间

每次提交前,我会检查:命名是否足够清晰,数据是否靠近使用位置,重复代码是否已经表现出稳定的共同模式。

架构不必预测所有未来。它只需要让下一次合理的修改,不必推倒整个项目。先让代码诚实地表达当前需求,再在变化中逐步找到秩序。

感谢你读到这里。

思考没有终点,下一篇见。