4.2 KiB
UI Engineering Principles v1(for Code Agents)
目标:在不指定框架/库细节的前提下,让 agent 自主做正确的 UI 工程决策。 输出要求:每次改动必须同时满足 可维护 / 可测试 / 可演进 / 可回滚。
- 先决策再动手(Decision Log)
在修改前,agent 必须在 PR 描述或注释里输出一段 Decision Log(最多 10 行):
组件边界如何划分(为什么)
状态放哪(为什么)
哪些是公共骨架,哪些是 slot/children 兼容性策略(是否破坏现有 API) 风险点与回滚点
不求长,但求明确。没有 Decision Log 视为不合格提交。
- 组件设计准则(边界与组合) 2.1 单一职责
一个组件只负责一个“稳定职责”
如果组件名需要用 “And/With/Plus” 连接多个职责 → 必须拆分
2.2 优先 Composition(组合)而不是 Config(配置)
优先通过 children / slots 组合 UI 结构
禁止为“结构差异”引入大量 boolean/enum props
只有在“结构稳定但样式/行为轻微差异”时才允许 variant
2.3 公共骨架 + 可插拔区域
抽取稳定的 Layout Skeleton 为 Root/Wrapper
差异点必须落在 children/slots(Header/Nav/Footer/Actions 等)
- 状态管理准则(少、近、可注入) 3.1 状态最小化
不新增“可从现有数据推导”的 state(用 derived/computed)
不新增“只服务渲染”的 state(用 CSS/DOM 或 memo)
3.2 状态就近原则
UI 局部状态(open/collapsed/hover)放组件内部
跨多个子组件共享但局部范围内 → Context(组件树内)
全局业务状态 → store/server(仅必要时)
3.3 可测试性要求
关键 UI 状态必须可通过 props 注入或通过 context mock
逻辑优先放 hook,视图保持“纯 render”
- 样式与可覆盖性(不锁死) 4.1 className 可组合
所有可复用组件必须接受 className 并合并
禁止写死样式导致无法覆盖(除非明确是私有组件)
4.2 语义优先
使用语义标签:nav/aside/header/main/button
可交互元素必须可键盘操作(button/link,不用 div 冒充)
4.3 选择器稳定
使用 data-* 或有限的 class 作为状态标记(如 collapsed/active)
避免复杂层级选择器导致脆弱
- 兼容性与演进(不破坏用户) 5.1 默认向后兼容
任何改动尽量不破坏旧 API(props/DOM 结构/路由)
若必须破坏:提供迁移方式或兼容层,并在说明中写清
5.2 小步可回滚
每个提交尽量原子化(Atomic Commit)
重构必须可分阶段:先抽公共,再迁移,再清理旧代码
- 性能准则(先可读,后优化) 6.1 不做“预优化”
禁止在没有证据时引入复杂 memo/useCallback/useMemo
若新增 memo:必须说明为什么必要(避免不必要渲染/昂贵计算)
6.2 可见性能问题再优化
性能优化必须是“局部、可验证、可回滚”的
- 测试准则(最小但关键) 7.1 测试优先覆盖“行为”,不是“实现”
测试用户可感知行为:导航高亮、折叠、权限可见性等
不测试内部实现细节(class 名、组件层级等)
7.2 最小覆盖要求
重构 UI:至少补 1 个关键路径测试(unit 或 integration)
引入新状态/交互:必须有测试覆盖
- 文档与可读性(未来的你也是用户) 8.1 代码即文档
组件 API 清晰可读(prop 名可解释)
复杂逻辑提取为函数/hook,并用短注释解释“为什么”
8.2 禁止“聪明代码”
避免迷宫式条件渲染
避免过度抽象(抽象必须带来复用或一致性收益)
- 输出格式要求(agent 必须遵守)
每次提交/变更输出:
Decision Log(≤10 行)
变更摘要(改了什么)
风险点(可能破坏什么)
验证方式(如何验证:命令 + 预期)
回滚方式(如何快速退回)
- 最终验收(Self-check)
Agent 在结束前必须自检并明确回答(是/否):
是否减少了重复,而不是引入更多分支?
是否让新增需求更容易通过组合实现?
是否状态更少、更近、更可测?
是否可覆盖样式并保持语义化?
是否可回滚(原子提交/分阶段)?
任意一项为“否”,必须解释原因或调整实现。