18 · 共用代码、组件边界与个例保护
适用:同一问题在多页重复出现、修改样式要逐页找、组件开始堆积条件分支,或为了复用把个别页面改坏。抽象的依据是职责、语义和变化原因。
ABS-01 · 按变化原因分层
| 层级 | 负责什么 | 适合统一调整的内容 |
|---|---|---|
| 基础变量 | 颜色、字号、间距、形状、动效尺度 | 品牌与可读性调整 |
| 基础控件 | 按钮、输入、对话框等语义和交互 | 焦点、禁用、忙碌、触控区域 |
| 组合模式 | 表单字段、状态区域、分页、页面标题 | 标签、错误位置、状态布局 |
| 领域模块 | 订单、项目、成员的规则与数据解释 | 名称校验、权限、错误映射 |
| 页面 | 模块组合、路由上下文、特有任务 | 页面流程与确实独有的布局 |
共享组件不应读取某个页面的路由或根据文案猜业务;基础按钮不应知道什么叫“退款”。页面也不应各自实现一份对话框焦点管理。
ABS-02 · 不同种类的重复分别抽象
纯计算抽函数;有生命周期的状态行为抽 hook/composable;可见结构与交互抽组件;多个页面共享的空间结构抽页面模式;业务限制归领域规则;可编辑文案按语义归内容模块。
两个片段长得相似不证明应该合并。订单总额与图标宽度都写了 24,变化原因完全不同。同一业务规则在提示、校验、计数器和测试中重复,才需要收敛到单一来源。
Vue 的组合式函数适合复用有状态逻辑,其生命周期副作用要清理;参见 Vue Composables。其他框架同样需要明确状态所有者、依赖与释放时机。
ABS-03 · 先用组合,再考虑复杂配置
<ProjectPageShell>
<PageHeader title="项目" />
<ProjectFilters v-model="filters" />
<ProjectList :items="items" :state="state" @retry="refresh" />
<Pagination v-model:page="page" :total="total" />
</ProjectPageShell>
这是边界示例,不是已提供的组件库。公共结构可组合,领域列表保留自身字段。避免一个 UniversalPage 接收几十个布尔值,根据路由名、用户角色和字段数量拼出所有页面。
变体表达稳定且有限的差异,例如按钮的 intent、size。业务插件或插槽表达内容扩展。若某个变体改变状态机、数据所有者或主要任务,应考虑独立的领域组件。
ABS-04 · 个例必须有明确作用域
exception:
id: canvas.toolbar.compact
base: 共享工具条
reason: 创作工作区需要给画布保留空间
overrides: [局部间距, 工具排列]
preserved: [可访问名称, 键盘操作, 焦点, 禁用行为]
scope: 画布工作区,不能影响设置与账单页面
verification: 窄屏、触控、键盘、长名称、深色
个例可以是局部 CSS 变量、受控变体或专有组件,不能依赖全局选择器覆盖其他页面。不要通过 !important、第几个子元素或“标题包含某个词”来识别业务例外。
ABS-05 · 拆分要形成可以解释的职责
当一个文件同时承担请求、权限、字段规则、文案、焦点和多个页面模式时,按职责拆分。不是达到某个行数就拆,也不是每三行都新建一个文件。
组件拆分后应能说清输入、输出、状态所有者和副作用;调用者不应为了使用它而知道其内部 DOM。保持 props 只读,业务更新通过事件或明确的状态接口发生。
避免把共享状态放在无意的模块单例里,导致不同页面实例互相污染。纯静态配置可以共享;草稿、计时器和请求控制器通常属于实例。
ABS-06 · 修改公共层必须检查调用方
变更前枚举引用页面、状态与变体,说明默认行为是否改变、个例是否仍成立。需要破坏性修改时提供迁移步骤,避免依赖页面自行补丁。
同一按钮在三处被复制,三处都修并不等于建立了共用代码。新增抽象后应迁移适用调用方,再移除旧实现;不适用的地方写清理由。
ABS-07 · 保留暂时重复的条件
需求尚未稳定、语义不同、未来变化方向不明时,可以暂时保留局部实现并记录候选。发现共同变化后再提取。不要为了减少行数建立一个谁也不敢改的公共框架。
检查:用一次实际改动验证边界,例如修改主控件高度。合理的结果是适用控件一起变化,画布等受保护的个例保持所需差异;不是逐页找数值,也不是整个网站一起被意外改动。