序章:我们还要发明多少次 Button?
从反复重写同一种组件出发,追问交互知识能否脱离具体技术被长期保存、复用和检验。
这一主题现已移至 一条可行路径,而不是唯一答案。
这一主题现已移至 Prototype 还不是宿主中的组件。
这一主题现已移至 原型库。
这一主题现已移至 从 Component 到 Prototype。
这一主题现已移至 用信息通路判断组件边界。
这一主题现已移至 为什么 feedback-only 可拆可不拆?。
这一主题现已移至 共同条件越多,比较越细。
这一主题现已移至 共同条件越多,比较越细。
这一主题现已移至 翻译层与生态。
这一主题现已移至 原型库。
这一主题现已移至 实践怎样修正当前近似。
毫不夸张地说,在 UI 交互组件的基础建设中,重复工作随处可见。
- 许多对 UI 有明确设计要求的团队都会定制自己的组件库。在这个过程中,Button、Switch、Select 等组件的基础行为经常被一遍遍重新实现。
- 当组织规模扩大,或产品需要运行在不同环境中时,同一个产品同时采用多种技术栈、又要求它们之间保持协调,也是一种很常见的需求。
这些工作可不只是把同一种颜色和圆角重新写几遍。
一再出现的交互责任
Section titled “一再出现的交互责任”Button 是这种重复最熟悉的缩影。为了看清反复出现的究竟是什么,我们再以一个带有持久状态的 Switch 为例。
无论它最终被实现为 React 组件、Vue 组件、Web Component、移动端的 widget,还是桌面 UI 工具包里的控件,它通常都要处理一批相似的问题:
- 表示并维持当前状态是 on 还是 off
- 响应由点击、触摸或键盘输入形成的激活操作
- 向用户反馈当前状态
- 处理焦点和禁用状态
- 暴露辅助技术可以识别的角色和状态
- 把状态变化或变化请求通知给使用它的应用
不过把上述交互需求用具体技术实现,代码层面差别就很大了。不同技术的结构、状态方案、事件系统和渲染方式可能相去甚远。
真正有意思的是:虽然实现方式迥然不同,但同样的交互需求却一次次重新出现在我们的待办列表里。
复用止步于生态边界
Section titled “复用止步于生态边界”如果项目处在一个足够成熟的单一技术生态中,这些重复通常能由现成的组件库,特别是 Headless 组件库缓解。比如说,Radix UI 在 React 生态中集中维护了许多交互逻辑和无障碍处理。它至少说明了一件事:在单个生态内部,把一部分交互知识从具体产品中抽出来、交给更多人共同维护,是有价值的。
但这还不能证明同一份知识可以直接跨越不同技术。此类复用通常有很明确的生态边界;一旦我们需要让多个技术选型保持协调,合适的基础就没那么好找了。然后我们可能会苦恼于:
- 我想获得 Radix UI 那样的无障碍交互支持,但项目不一定能使用 React;
- 我想延续 shadcn/ui 的视觉表达,但当前技术栈没有足够成熟的社区实现,只能重新完成相应的呈现和交互;
- 公司已经维护了一套 React 组件库,但由于新的业务需要,现在还得继续实现 Vue、Web Components、移动端或桌面端的版本。
这些困扰并不完全属于同一类问题。视觉风格需要在具体渲染系统中重新投影;交互和无障碍责任则要重新接入宿主的输入、焦点与事件机制。它们都会带来重复建设,但可以跨技术保存的范围和保持一致的条件并不相同。
这当然不是现有组件库做得不够好,它们本来就在解决各自生态里的问题。只是当交互知识只能以某种具体实现被使用时,它就很难自然惠及其他技术;等到团队更换框架或平台,原有代码或许还在,维护、运行和迁移它的条件却可能已经消失,许多解决过的问题仍然需要重新解决。
因此,要长期在多种实现中维持约定的视觉表现、交互细节和无障碍质量,通常需要持续而昂贵的专门投入。保持这些一致性,一直都“不便宜”。
可这些工作真的必须随着每一次技术更替,从头再做一遍吗?
一个仍需检验的猜想
Section titled “一个仍需检验的猜想”反复出现的责任,并不能直接证明它们一定能够形成跨技术的公共抽象。不同平台之间的差异,也可能只是把原先分散在各个实现里的复杂度转移到一个新的中间层。增加一层抽象,也不会自动带来一致性或可靠的无障碍支持。
因此,我们提出的不是一个已经成立的结论,而是一个更有限、仍需检验的猜想:
在人机交互中,是否存在一些不必随实现技术一起重写的稳定逻辑?我们能否把它们找出来、清楚地记录,再放进不同技术中检验它们是否仍然成立?
如果这个猜想成立,一部分交互知识就有机会成为可以长期积累和共享的公共资产,而不再被某个框架、平台或技术生命周期独占。如果它在某些组件、媒介或平台上不成立,那些失败也会帮助我们看清这条边界究竟在哪里。
这就是 Proto UI 想要探索的可能性。接下来的正文会围绕如何识别、表达、翻译和检验这些交互知识展开。
不过,在讨论如何保存它们之前,我们还需要回答一个更基础的问题:如果代码、节点、控件乃至整棵渲染树都不是那个稳定的对象,那么当我们指着不同实现说“它们是同一种组件”时,我们认出的究竟是什么?