第3章 架构
我们从基础讲起!相信你对图形用户界面已经非常熟悉了——手机、电脑以及大多数可交互的电子设备上都能见到它。事实上,你现在很可能就是通过图形界面在阅读这本书。
图形用户界面本质上是把信息展示给用户的应用。用户则可以选择与应用交互——通常借助键盘、鼠标或触摸屏等设备。
用户的交互可能促使应用更新并展示新的信息,而这又可能引发进一步的交互,进而带来进一步的更新……如此循环往复。正是这种快速反馈的循环,造就了我们所说的交互性。
注意:在本书中,图形用户界面会被称为 GUI、UI、用户界面,或简称界面。严格来说,并非所有界面都是图形化的,也不都面向用户;但结合本书的语境,这些术语可以互换使用。
拆解一个界面
既然我们的目标是创建用户界面,不妨仔细观察一下它的构成。先从一个非常简单的例子入手:经典的计数器界面。它由什么组成?
可以清楚地看到,这个界面包含三个视觉上相互独立的元素:两个按钮,中间夹着一个数字。我们把界面上这些可见的独立元素称为widget或元素。
有些widget是可以交互的,比如按钮。在计数器界面中,按钮可以触发特定的交互:上面的按钮用来增加计数值,下面的按钮用来减少计数值。
我们也可以说,用户界面是有状态的——在交互之间存在某些状态。计数器界面显示一个数字,代表计数值。显示的数字会根据我们点击按钮的次数而变化。点击一次递增按钮,与点击两次,显示的结果会不同。
GUI 三位一体
我们对界面的快速拆解,识别出了用户界面的三个基本概念:
- 组件(Widgets) — 界面中独立的视觉元素。
- 交互(Interactions) — 由组件触发的操作。
- 状态(State) — 界面的底层条件或信息。
这些概念彼此关联,形成了另一个反馈循环!
组件在用户操作时产生交互。这些交互随后改变界面的状态。变化后的状态会传播并决定需要显示的新组件。这些新组件又可能产生新的交互,再次改变状态……如此循环。
这些概念及其关联构成了用户界面的基础架构。因此,创建用户界面必然意味着定义这些组件、交互和状态,以及它们之间的连接关系。
概念不同,特性不同
接口的三个基本概念在复用性上差异很大。
界面的状态和交互非常具体地取决于应用程序及其用途。如果告诉你一个界面包含一个数值,以及递增和递减交互,你很容易猜到我说的是计数器界面。
但如果你告诉我,我有一个包含两个按钮和一个数字的界面……那你猜它具体是哪种界面就难多了。它可以是任何东西!
这是因为小部件(widget)通常非常通用,因而也更具复用性。大多数界面都是由常见的小部件——如按钮和数字——组合而成的。事实上,用户习惯于这些通用小部件以特定方式运作。如果它们表现异常,界面就会显得不直观,并带来糟糕的用户体验。
尽管小部件本身通常很通用;但应用程序状态及其交互所规定的具体小部件配置,却具有极强的应用特异性。按钮是通用的;但一个标签为“+”、点击后会使数值递增的按钮,就非常具体了。
所有这些意味着,在构建特定用户界面时,我们不应专注于实现每一个通用小部件及其行为。相反,应将小部件视为可复用的构建块——它们独立于我们的应用程序,由某个库提供——而把注意力集中在基础架构中那些应用特定的部分:状态、交互、交互如何改变状态,以及状态如何决定小部件。
Elm 架构
事实证明,界面架构中这四个应用特定的部分,恰恰也是Elm 架构的四个核心思想。
Elm 架构是一种设计交互式程序的范式,它自然地涌现于 Elm——一门用于构建可靠 Web 应用的、令人愉悦的纯函数式编程语言。
在纯函数式语言中涌现的模式和思想,在 Rust 中往往也非常适用,因为它们充分利用了不可变性和引用透明性——这两者都是极具价值的属性,不仅使代码易于推理,还能与借用检查器(borrow checker)良好配合。
此外,The Elm Architecture 不仅在 Elm 中自然形成,当我们单纯地拆解用户界面、归纳其内部运作机制时(就像本章刚做的那样),它也会自然而然地浮现。
The Elm Architecture 对其基本组成部分采用了不同的、甚至可以说更精确的命名:
- Model — 应用的状态。
- Messages — 应用的交互。
- Update logic — 消息如何改变状态。
- View logic — 状态如何决定界面组件。
这些名称虽然不同,但指向的正是我们已经发现的那些基本概念,因此可以互换使用。
注意:在 iced 中,state 和 messages 这两个名称比 model 和 interactions 更为常用。