- 原文地址:UNIDIRECTIONAL USER INTERFACE ARCHITECTURES
- 原文做者:André Staltz
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:EmilyQiRabbit
- 校对者:Hopsken,dandyxu
本文对所谓的“单向数据流”架构进行了非详尽的概述。这并不意味着本文应被视为一个初学者教程,它更应该是一个架构之间的差别和特性的概述。最后,我将会介绍一个和其余框架显著不一样的新框架。本文仅假设客户端是 Web UI 框架。html
若是没有术语的共识,讨论这些框架可能会形成困惑,因此咱们做出以下的假设:前端
用户事件(User events) 是来自用户直接操做的输入设备的事件。好比:鼠标点击,鼠标滚动,键盘按键,屏幕触摸等等。react
当不一样的框架使用 “View” 这个术语时,含义可能大不相同。做为替代,咱们使用 “rendering” 来表明共识中的 “View”。android
**用户接口渲染(User interface rendering)**指代屏幕上的图形输出,通常状况下用 HTML 或者其余相似的高级声明代码好比 JSX 来描述。ios
一个**用户界面(UI)程序(User interface (UI) program)**是任何一个将用户事件做为输入输出视图的程序,这是一个持续的过程而不是一次性的转换。git
假定 DOM 以及其余层好比一些框架和库存在于用户和架构之间。github
模块间箭头的所属很重要。A--> B
和 A -->B
是不同的。前者是被动编程,然后者是反应式编程。这里能够阅读更多。编程
若是子组件和总体的结构一致,这个单向架构就被称为分形(fractal)。redux
在分形架构中,总体能够像组件同样简单地打包而后用于更大的应用。后端
在非分形架构中,那些不重复的部分被称为协调器(orchestrators),它们不属于具备分级结构的部分。
第一个必须提到的是 Flux。它虽然不是绝对的先驱,可是至少在流行度上,对于不少人它都是第一个单向架构。
组成部分:
特色:
Dispatcher。 由于它是事件的载体,它是惟一的。不少 Flux 的变体去掉了对 dispatcher 的需求,其余的一些单向框架也没有 dispatcher 等同物。
只有 View 有可组合组件。 分级结构仅存在于 React 组件中,Stores 和 Actions 都没有。一个 React 组件就是一个 UI 程序,而且其内部一般不会编写成一个 Flux 架构的形式。因此 Flux 不是分形的,Dispatcher 和 Stores 做为它的协调器。
用户事件处理器在 rendering 中声明。 换句话说,React 组件的 render()
函数处理和用户交互的两个方向:渲染和用户事件处理(例如 onClick={this.clickHandler}
)
Redux 是一个 Flux 的变体,单例 Dispatcher 被改编成了一个独一的 Store。Store 不是从零开始实现的,相反,建立它的方式是给 store 工厂一个 reducer 函数。
组成部分:
dispatch(action)
函数特色:
store 工厂。 使用工厂函数 createStore()
能够建立 Store,由 reducer 函数做为组成参数。还有一个元工厂函数 applyMiddleware()
,接受中间件函数做为参数。中间件是用附加的链式功能重写 store 的 dispatch()
函数的机制。
Providers。 对于用来做为 UI 程序的 “View” 框架,Redux 并不武断控制。它能够和 React 或者 Angular 或者其余框架配合使用。在这个框架中,“View” 是 UI 程序。和 Flux 同样,Redux 被设计为非分形的,而且以 Store 做为协调器。
用户事件处理函数的声明可能在也可能不在 rendering。 取决于当下的 Provider。
Famous Framework 引入了 Behavior-Event-State-Tree (BEST),它是一个 MVC 的变体,BEST 中 Controller 分红了两个单向元素:Behavior 和 Event。
组成部分:
特色:
多范例。 State 和 Tree 是彻底声明式的。Event 是急迫性的,Behavior 是功能性的。一些部分是响应式的,而其余部分则是被动式的。(例如,Behavior 会对 State 做出反应,Tree 则对 Behavior 比较消极)
Behavior。 Behavior 将 UI 视图(Tree)和它的动态属性分离了,这在本文中的其余几个框架中都不会出现。据称,这出于不一样的考虑:Tree 就比如 HTML,Behavior 就比如 CSS。
用户事件处理的声明从视图分离。 BEST 是极少的不将用户事件处理和视图关联的单向框架之一。用户事件处理属于 Event,而不是 Tree。
在这个框架中,“View” 是一个树结构,一个 “Component” 是一个 Behavior-Event-Tree-State 元组。组件是 UI 程序。BEST 是分形框架。
也被称为 “The Elm Architecture”,Model-View-Update 和 Redux 很类似,主要由于后者是受这个框架启发的。这是一个纯函数的框架,由于它的主语言是 Elm,一个 Web 的函数式编程语言。
组成部分:
特色:
处处都是分级结构。 以前的几个框架只在 “View” 中有分级结构,可是在 MVU 架构中这样的结构在 Model 和 Update 中也能找到。甚至是 Actions 可能也嵌套了 Actions。
组件分块导出。 由于哪里都是分级结构,在 Elm 架构中的 “component” 是一个元组,包括了:模块类型,一个初始模块实例,一个 View 函数,一个 Action 类型,一个 Update 函数。纵览整个架构,不可能有组件从这个结构中偏离。每一个组件都是 UI 程序,而且这个架构是分形的。
Model-View-Intent 是基于框架 Cycle.js 的主要架构模式,它同时也是基于观察者 RxJS 的彻底反应单向架构。可观察(Observable) 事件流是一个全部地方都用到的原函数,Observables 上的函数是架构的一部分。
组成部分:
特色:
极大的依赖于 Observables。 该框架每一部分的输出都被描述为 Observable 事件流。所以,若是不用 Observables,就很难或者说不可能描述任何 “data flow” 或 “change”。
Intent。 和 BEST 中的 Event 大体类似,用户事件处理在 Intent 中声明,从视图中分离出来。和 BEST 不一样,Intent 建立了 actions 的 Observable 流,这里的 actions 就和 Flux,Redux,和 Elm 中的相似。可是,和 Flux 等中的不一样的是, MVI 中的 actions 不直接被发送到 Dispatcher 或 Store。它们就是简单的能够直接被模块监听。
彻底反应。 用户视图反应到视图输入,视图输出反应到模块输出,模块输出反应到 Intent 输出,Intent 输出反应到用户事件。
MVI 元组是一个 UI 程序。当且仅当全部用户定义元素与 MVI 一块儿应用时,这个框架是分形的。
这篇博文将 Nested Dialogues 做为一个新的单向架构来介绍,适用于 Cycle.js 和其余彻底依赖于 Observables 的方法。这是 Model-View-Intent 架构的一次进化。
从 Model-View-Intent 序列能够函数化组合为一个函数这个特性提及,一个 “Dialogue”:
如图所示,一个 Dialogue 是一个将用户事件的 Observable 做为输入(Intent 的输入),而后输出一个视图的 Observable(View 的输出)的方法。所以,Dialogue 就是一个 UI 程序。
咱们推广了 Dialogue 的定义来允许用户以外的其余目标,每个目标都有一个 Observable 输入和一个 Observable 输出。例如,若是 Dialogue 经过 HTTP 链接了用户和服务端,这个 Dialogue 就应该接受两个 Observables 做为输入:用户事件的 Observables 和 HTTP 响应的 Observables。而后,它将会输出两个 Observables:视图的 Observables 和 HTTP 请求的 Observables。这个是 Cycle.js 里面 Drivers 的概念。
这就是 Model-View-Intent 做为 Dialogue 重组后的样子:
要想将 Dialogue 方法做为一个更大程序的 UI 程序子组件重复使用,这就涉及到 Dialogue 之间的嵌套问题:
Observables 在 Dialogues 不一样层之间的链接是一个数据流图。它并没必要须是一个非周期图。在例如子组件动态列表这样的实例中,数据流图就必须是周期的。这样的例子超出了本文的讨论范围。
嵌套的 Dialogues 其实是一个元架构:它对组件的内部结构没有约束,这就容许咱们将前文所述的全部架构嵌入一个嵌套的 Dialogue 组件中。惟一的约束涉及 Dialogue 的一端的接口:输入和输出都必须是一个或一组 Observable。若是一个结构如同 Flux 或者 Model-View-Update 的 UI 程序可以让它的输入和输出都以 Observables 呈现,那么这个 UI 程序就可以做为一个 Dialogues 函数嵌入一个嵌套的 Dialogues。
所以,这个架构是分形的(仅涉及 Dialogue 接口时)、通常性的。
能够查看 TodoMVC implementation 和 this small app 做为使用了 Cycle.js 的嵌套 Dialogues 的例子。
尽管嵌套 Dialogues 的通常性和优雅性在理论上能够用来做为子组件嵌入到其余架构中,但我对这个框架最主要的兴趣在于构建 Cycle.js 应用。我一直在寻找一个天然且灵活的 UI 架构,而且同时可以提供 结构。
我认为嵌套的 Dialogues 是天然的,由于它直接表现了其余典型 UI 程序完成的:一个将用户事件做为输入(输入 Observable)持续运行的进程(Observable 就是持续的进程),而且产生视图做为输出(输出 Observable)。
它也是灵活的,由于正如咱们所见,Dialogue 的内部结构能够自由的应用于任何模式。这和有着死板结构做为条框的 Model-View-Update 截然相反。分形架构比非分形的更加易重用,我很高兴嵌套的 Dialogues 也有这个属性。
可是,一些常规的结构也能够对引导开发有所帮助。虽然我认为 Dialogue 的内部结构应当是 Flux,但我想 Model-View-Intent 很天然的适配了 Observable 的输入输出接口。因此当我想自由一些,不把 Dialogue 做为 MVI 时,我认可大部分时间我都会把它构形成 MVI。
我不想自大的说这是最好的用户界面架构,由于我也是刚刚发现了它而且依旧须要实际应用来发现它的优缺点。嵌套 Dialogues 仅仅是我如今的最强烈推荐。
若是你喜欢这篇文章,分享给你的 followers:(tweeting)。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。