第一章:代码之前的组件
当 class、function、DOM、widget 或渲染树都改变后,我们凭什么仍说两个实现属于同一种组件?
这一主题现已移至 Prototype 还不是宿主中的组件。
这一主题现已移至 从 Component 到 Prototype。
这一主题现已移至 Adapter。
这一主题现已移至 Prototype 还不是宿主中的组件。
这一主题现已移至 Prototype 还不是宿主中的组件。
这一主题现已移至 Proto UI 选择的三条主线。
开发者在“还原”和“实现”什么?
Section titled “开发者在“还原”和“实现”什么?”让我们从软件开发里一件很普通的事情说起。
比如说,假设我们有一个 Flutter + WebView 的移动端 App。WebView 承载了一个变化很频繁的子业务,但其中的基础组件——按钮、开关、弹窗等——应该和 Flutter 部分保持高度一致。
具体实现时,React 和 Flutter 的区别当然很大。毕竟它们的语法、布局方式、单位体系、事件系统都不太一样。不过,如果你去问产品经理或者设计师,这两个实现是不是来自同一份组件设计,只要“还原度”足够高,他们通常会回答是。如果回答不是,那负责开发的人大概就又有工作要做了。
这里很有意思:开发者实现组件时,通常是在“还原”某个东西,这个东西可能来自设计图、原型稿、需求文档,也可能来自团队对组件已经形成的共识。
也就是说,在选择 React、Flutter 或其他具体技术之前,我们通常已经对组件抱有一些预期。当然我们未必一开始就知道全部细节,实际开发中也经常会暴露设计中的遗漏,但这并不影响我们发现——这些预期并不完全属于某一种具体技术。
这个假设场景也是序章提到的重复劳动之一。我们常常需要用不同的技术反复实现相似的组件,还需要判断它们在哪些地方应该保持一致。Switch 还算是一个简单的例子;如果换成弹窗,那视觉、常见操作、焦点管理、辅助技术相关行为等方面在 Flutter 和 Web 之间都可能出现难以调和的差异。对于确实需要保持跨技术产品体验一致的团队来说,解决这些差异并不轻松。
本章暂时不讨论“一致”到底要精确到什么程度。在这个假设场景里,我们只需要先注意到:不同实现之间存在一组可以比较的预期,而它们不等于其中任何一份代码。
组件到底是什么?
Section titled “组件到底是什么?”这个问题很难回答。
- 是 class、function、widget 或文件吗?当然可以这么说,只是把它们说成组件的实现形式会更准确。
- 是一组带有视觉约束的功能集合吗?这可能很接近答案了。从行为和现象出发,无需追问某种形而上的本质。
- 是一种意图、一种符号或一份规范吗?也有可能,不过这类描述不一定能直接变成实现。就像 ARIA 相关规范与实践指南可以帮助说明辅助技术所需的角色、状态与行为,却不会自动替我们生成符合要求的组件。
Proto UI 给出的工作假设是:我们可以先把组件看作一个相对稳定的交互主体。
所谓交互主体,是说组件并不孤立存在。它会被人感知或操作,会接受软件制作者的配置,会向应用报告变化,也可能和其他组件交换信息。我们之所以能够在不同技术中认出它,是因为这些关系和责任还保持着某种可辨认的连续性,而不是因为它始终由同样的 class、节点或渲染树实现。
这并不排斥从功能和表现出发去“归纳”组件。恰恰相反,视觉反馈、交互行为、角色语义、配置方式和对外结果,都是我们认识组件的重要证据。比如在 Proto UI 当前仍待实践检验的区分中,Switch 是持久的 on/off value control,Toggle 是 button-like 的持久 active control,Checkbox 则是以 checked 为主要输入值、还可以表达 mixed/indeterminate 展示状态的 checked input control。
问题主要出在“完备”上。在实现之前,我们很难知道这份功能清单是不是已经没有遗漏;当新的平台、交互媒介或无障碍需求出现时,过去没写下来的责任也可能突然变得重要。结构化的模型能帮助我们发现遗漏、关系与宿主适配差异,却不会自动回答信息传递的方向与时序、宿主能力边界或翻译损失。它仍然需要在具体实现中接受检验。
因此,Proto UI 的工作不是用一套自顶向下的哲学替代实践归纳,而是在两者之间反复工作:先用一套模型组织我们正在寻找的东西,再通过真实组件和不同技术实现检查这套模型,发现错误以后继续修正。
如果后文偶尔使用组件“本质”这样的说法,它指的也是一种用于跨技术检验、并接受实践修正的当前近似,而不是一个已经被证明、永远不会改变的终极答案。
但我们如何描述它?
Section titled “但我们如何描述它?”回到 Switch。我们知道它用于表达 on/off 状态,可以被点击或被键盘操作,会向用户显示当前状态,通常还需要让辅助技术识别它的角色。继续往下,我们还能列出它接受哪些配置、怎样向应用报告变化,以及在不同情况下分别给出什么反馈……
一个一个罗列并没有错。如果清单真的完整,它同样能够描述 Switch。麻烦在于,我们很难判断它什么时候才算完整,也不容易从一份平铺的清单里看出遗漏与问题。
Proto UI 选择先从组件与外界的关系入手:是谁在向谁传递信息?信息沿着什么方向流动?这类信息承担怎样的责任与语义?Proto UI 不先按 API 名称做分类,而是从参与者身份、传递方向和语义责任中推导出一种组织原则,并把由此形成的关系称为“信息通路”(information channel)。
信息通路构成了组织交互主体与外部参与者关系的骨架,但它并不解释组件的一切现象。状态如何保存、语义如何随时间展开、复杂组件内部如何组织与协作,之后还会需要其他概念来解释。
不过,在继续讨论那些问题之前,我们得先看组件正在和谁发生关系,以及信息怎样沿着这些关系传递。这就是下一章要讨论的内容。