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