术语表¶
响应式编程¶
一种编程风格,其中数据或事件被推送到处理它们的逻辑,而不是由逻辑从源头拉取数据和事件。它是将程序逻辑表示为对数据流执行的一系列操作,这些操作在订阅该流处于活动状态时执行。
单向数据流¶
数据从业务逻辑沿单一路径流向 UI,并沿该路径全程单向流动。事件从 UI 沿单一路径流向业务逻辑,并沿该路径全程单向流动。因此,图中有两组单独处理的有向边,并且这两组自身都没有环或后向边。
声明式编程¶
声明式程序声明了它希望系统达到的状态,而不是如何实现该状态。
命令式编程¶
命令式程序的代码是一系列语句,这些语句根据特定事件直接改变程序的状态。
状态机¶
一种抽象,将程序逻辑建模为由一组状态及其之间的转换(边)构成的图。参见:en.wikipedia.org/wiki/Finite-state_machine
幂等¶
指函数多次调用产生的副作用不会重复,其结果纯粹是输入的函数。换句话说,如果使用相同的输入多次调用,结果将是相同的。对于 Workflows,render()
函数必须是幂等的,因为 runtime 对其可能被调用的次数不提供任何保证。
Workflow Runtime¶
执行工作流树的事件循环。在每次过程(pass)中
-
通过在工作流树的每个 Node 上调用
render()
来组装一个 Rendering,每个父工作流都可以选择将其子级的 Renderings 合并到自己的 Rendering 中。 -
事件循环等待 Action 被发送到 Sink。
-
该 Action 为创建它的 Workflow 提供一个(可能更新的)State,并可能产生一个 Output。
-
任何发出的 Output 都会依次由更新的 Workflow 的父级定义的 Action 处理,再次可能更新其 State 并发出 Output,沿着层级向上级联。
-
对整个工作流树使用更新后的 States 进行一次新的
render()
过程(pass)。
我们使用 Workflow Runtime 一词指代框架中执行此事件循环、响应 Actions 和调用 render()
的核心代码。
工作流(实例)¶
一个对象,它有效地将状态机的转换和副作用定义为两个函数:
- 提供初始 State:(Props)-> State
- 提供 Rendering:(Props 和 State)-> (Rendering、副作用调用 和 子工作流调用)
render 函数声明的子工作流调用会依次调用子级的 render()
函数,从而允许父级 render 函数选择将子级的 Rendering 值合并到自己的 Rendering 中。
Workflow 本身不是状态机,理想情况下也没有自己的 State。它更像是一个 schema,用于标识特定类型的状态机,该状态机可以通过 Workflow Runtime 在 initialState()
中启动,并通过重复调用 render()
来推进。
注意:使用“Workflow”一词存在显著的模糊性,它有时可以指声明 Workflow 行为的类/结构体,也可以指代表正在运行的 Workflow Node 的对象。要理解 Runtime 行为,掌握这种区别是必要且有价值的。在使用 Workflow 时,正式的区别不如其运行方式的心智模型有价值。
工作流(节点)¶
一个活跃的状态机,其行为由 Workflow Instance 定义。这是由 Workflow Runtime 持有的对象,其 State 根据 Workflow Instance 中声明的行为进行更新(或“驱动”)。在 Kotlin 和 Swift 中,Workflow Node 是通过私有的 WorkflowNode
类/结构体实现的。
工作流生命周期¶
每个 Workflow 或 Side Effect Node 都有一个由其父级决定的生命周期。对于根 Workflow,其生命周期由根的 host 选择使用来自根 Workflow 的 Renderings 流的时长决定。对于非根 Workflow 或 Side Effect——即子级的情况——其生命周期确定如下:
-
开始:父级在其自身的
render()
过程中首次调用子级时。 -
结束:随后的第一个未调用子级的
render()
过程。
注意,在开始和结束之间,Workflow 或 Side Effect 并非在每次 render()
过程中都被“重新调用”以重新开始,而是最初调用的实例继续运行,直到进行一次未调用它的 render()
调用。
工作流树¶
共享一个根节点的工作流节点树。工作流节点可以有子节点并形成层级结构。
工作流根节点¶
工作流树的根节点。它由一个 host 拥有,该 host 使用特定的 Workflow 实例启动 Workflow Runtime。
RenderContext¶
从工作流 render 方法提供对 Workflow Runtime 访问权限的对象。提供三种服务:
-
一个用于接受 WorkflowActions 的 Sink
-
递归地渲染工作流子级
-
执行 Side Effects
渲染过程¶
Workflow Runtime 事件循环的一部分,它遍历工作流树,并在每个工作流节点上调用 render()
。当 RenderContext Sink 接收到 Action 时,会发生 Action 级联,并且在 Action 级联完成后发生 Render Pass。
输出事件¶
当子工作流发出 Output 值时,这是一个 Output 事件。在调用子工作流时会注册 Handlers,用于将子级的 Output 值转换为 Actions,从而推进父级的 State。
UI 事件¶
程序 UI 中发生的任何事件——例如,点击、拖动、按键——其监听器已连接到工作流的 Rendering 中的回调。UI 事件回调通常会将 Actions 添加到 Sink 中,以推进工作流的 State。
Action¶
与特定工作流(实例)相关联的类型,负责将给定的 State 转换为新的 State,并可选地发出 Output。Actions 被发送到 Sink 由 Workflow Runtime 处理。
Action 级联¶
当事件发生且 handler 提供一个 Action 时,该 Action 可能会为父工作流产生一个 Output,而父工作流自身的 handler 又提供一个 Action,该 Action 可能产生一个 Output,如此向上级联直至工作流树的顶端。这就是 Action 级联。
Sink¶
RenderContext 提供的句柄,用于将 Actions 发送给 Workflow Runtime。Workflow Runtime 应用这些 Actions 来推进工作流的 State,并可选地产生 Output,由其父级注册的 handler 进行处理。
Props¶
特定工作流的输入属性集。这是由其父级提供给子工作流,或由其 host 提供给根工作流的公共 State。
-
对于 Swift:实现工作流的结构体上的属性集。
-
对于 Kotlin:工作流签名中的参数类型 PropsT。
在 Kotlin 中,Props 与其他依赖项(通常作为构造函数参数提供)之间有正式的区别。
State¶
工作流实现的内部 State 类型。
“不可变” State¶
State 对象本身是不可变的,换句话说,其属性值不能被更改。
这对于 Workflows 意味着 Workflow Runtime 持有每个工作流内部 State 的一个规范实例。工作流的 State 在调用 Action 时返回的新实例原子性地替换该规范实例时被“推进”。State 只能通过 WorkflowAction 进行变异,这将触发重新渲染。以这种方式保持 State 不可变有许多好处:
-
推理和调试工作流更容易,因为对于任何给定的 State,都会有一个确定的 Rendering,并且 State 不能改变,除非作为
render()
方法的新参数值。 -
这有助于使
render()
幂等,因为 State 在该函数执行过程中不会被修改。
注意,这种不可变性只能通过约定来强制执行。虽然可以作弊,但强烈不鼓励这样做。
Rendering¶
工作流 State 的外部可用公共表示。它可能包含事件处理函数。在工作流签名中给定一个具体类型。
注意,此“Rendering”不一定代表程序的 UI。“Rendering”仅仅是工作流的已发布 State,也可以只是数据。通常这些数据用于渲染 UI,但也可以用于其他方面——例如,作为服务 API 的实现。
Output¶
可选地由 Action 交付给工作流的父级或根工作流的 host 的对象类型。
子工作流¶
有父级的工作流。父级可以将子工作流的 Rendering 组合到其自身的 Rendering 中。
副作用¶
在 render()
中,可以使用给定的 key 调用 runningSideEffect()
,该函数将由 Workflow Runtime 调用一次。
-
对于 Swift,还会将一个 Lifetime 对象传递给
runningSideEffect()
,该对象具有一个onEnded()
闭包,可用于清理。 -
对于 Kotlin,使用一个 coroutine scope 来执行该函数,以便在清理时可以将其
cancelled()
。考虑到任何属性(包括 Sink)都可以被 Side Effect 的闭包捕获,这是可用于与异步(且通常是命令式的)工作流子级交互的基本构建块。
Worker¶
一种只提供 output 而不提供 rendering 的子工作流——它是一种在 Workflows 中执行异步工作的模式。
-
对于 Kotlin,这是一个实际的 Interface,它提供了一种方便的方式来指定产生 Output 的异步工作以及该 Output 的 handler(该 handler 可以提供 Action)。有 Kotlin 扩展可以将 Rx Observables 和 Kotlin Flows 映射以创建 Worker 实现。
-
对于 Swift,至少有 3 种不同的 Worker 类型,它们是 reactive API 的便利包装器,用于简化执行工作。
View¶
图形用户界面系统中管理二维框的类或函数,能够绘制显示屏的定义区域并响应其范围内的用户输入事件。Views 排列在一个层级树中,父级能够布局子级并管理它们的绘制和事件处理。
Workflow 支持的实例有:
-
对于 Kotlin
- 传统 Android:
class android.view.View
- Android JetPack Compose:
@Composable fun Box()
- 传统 Android:
-
对于 Swift:
class NSViewController
Screen¶
一个接口/协议,用于标识建模 View 的 Renderings。Workflow UI 库可以将给定的 Screen 类型映射到能够显示一系列此类 Screens 的 View 实例。
在 Kotlin 中,Screen
是一个标记接口。Android UI 库将每个类型 S : Screen
映射到一个能够执行以下操作的 ScreenViewFactory<S>
:
- 创建
android.view.View
的实例,或 - 提供一个可在
Box {}
上下文中调用的@Composable fun Content(S)
函数。
注意,Android UI 支持能够无缝地交错绑定到 View
或 @Composable
的 Screens。
在 Swift 中,Screen
协议定义了一个单一函数,用于创建 ViewControllerDescription
实例,这些对象创建和更新 ViewController
实例以显示相应类型的 Screens。
Overlay(仅限 Kotlin)¶
一个接口,用于标识建模为覆盖基础 Screen 的平面的 Renderings,可能承载另一个 Screen——“覆盖”是指它们具有更高的 z-index,用于可见性和事件处理。
在 Kotlin 中,Overlay
是一个标记接口。Android UI 库将每个类型 O : Overlay
映射到一个能够创建和更新 android.app.Dialog
实例的 OverlayDialogFactory<O>
。
容器 Screen¶
一种设计模式,描述一种 Screen 类型,其实例包装一个或多个其他 Screens,通常用于注释这些 Screens 或定义它们之间的关系。
将一个 Screen 包装在另一个 Screen 中并不一定意味着派生的 View 层级结构会改变。通常,绑定到 Container Screen 的 Kotlin ScreenViewFactory
或 Swift ViewControllerDescription
会将其构造和更新工作委托给被包装 Screens 的对应工厂或描述符。
容器 View¶
能够承载由 Screen renderings 驱动的子级的 View。Container View 通常由特定类型的 Container Screens 驱动——例如,一个可以显示 BackStackScreen 值的 BackStackContainer View。例外情况是根 Container View,它能够显示任何类型的 Screen 实例系列。
-
对于 Kotlin,根 Container Views 是
WorkflowLayout : FrameLayout
和@Composable Workflow.renderAsState()
。用于显示自定义 Container Screens 的自定义 Container Views 可以使用WorkflowViewStub : FrameLayout
或@Composable fun WorkflowRendering()
来显示被包装的 Screens。 -
对于 Swift,根 Container View 是
ContainerViewController
。用于渲染自定义 Container Screens 的自定义 Container Views 可以构建为ScreenViewController
的子类,并使用DescribedViewController
来显示被包装的 Screens。
ViewEnvironment¶
一个只读的键/值映射,在更新时从 Container View 传递给其子级,其精神类似于 Swift UI 的 EnvironmentValues
和 Jetpack 的 CompositionLocal
。与它们类似,ViewEnvironment 主要旨在允许父级向子级提供有关它们显示上下文的提示——例如,允许子级知道它是否是返回栈的成员,从而决定是否显示“返回”按钮。
ViewEnvironment 也可以慎重地用作 UI 特定关注点(例如图像加载器)的服务提供者——请谨慎使用。