[譯] 單向用戶界面架構

單向用戶界面架構

本文對所謂的「單向數據流」架構進行了非詳盡的概述。這並不意味着本文應被視爲一個初學者教程,它更應該是一個架構之間的差別和特性的概述。最後,我將會介紹一個和其餘框架顯著不一樣的新框架。本文僅假設客戶端是 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--> BA -->B 是不同的。前者是被動編程,然後者是反應式編程。這裏能夠閱讀更多。編程

若是子組件和總體的結構一致,這個單向架構就被稱爲分形(fractal)redux

在分形架構中,總體能夠像組件同樣簡單地打包而後用於更大的應用。後端

在非分形架構中,那些不重複的部分被稱爲協調器(orchestrators),它們不屬於具備分級結構的部分。

FLUX

第一個必須提到的是 Flux。它雖然不是絕對的先驅,可是至少在流行度上,對於不少人它都是第一個單向架構。

組成部分:

  • Stores:管理事務信息和狀態
  • View:一個 React 組件的分級結構
  • Actions:由 View 當中觸發的用戶事件而產生的事件
  • Dispatcher:搭載全部 actions 的事件

Flux diagram

特色:

Dispatcher。 由於它是事件的載體,它是惟一的。不少 Flux 的變體去掉了對 dispatcher 的需求,其餘的一些單向框架也沒有 dispatcher 等同物。

只有 View 有可組合組件。 分級結構僅存在於 React 組件中,Stores 和 Actions 都沒有。一個 React 組件就是一個 UI 程序,而且其內部一般不會編寫成一個 Flux 架構的形式。因此 Flux 不是分形的,Dispatcher 和 Stores 做爲它的協調器。

用戶事件處理器在 rendering 中聲明。 換句話說,React 組件的 render() 函數處理和用戶交互的兩個方向:渲染和用戶事件處理(例如 onClick={this.clickHandler}

REDUX

Redux 是一個 Flux 的變體,單例 Dispatcher 被改編成了一個獨一的 Store。Store 不是從零開始實現的,相反,建立它的方式是給 store 工廠一個 reducer 函數。

組成部分:

  • Singleton Store:管理狀態,並擁有一個 dispatch(action) 函數
  • Provider:Store 的訂閱者,和像 React 或者 Angular 這樣的 「View」 框架交互
  • Actions:由用戶事件建立的事件,而且是根據 Provider 而建立
  • Reducers:純函數,根據前一狀態和一個 action 得出新的狀態

Redux diagram

特色:

store 工廠。 使用工廠函數 createStore() 能夠建立 Store,由 reducer 函數做爲組成參數。還有一個元工廠函數 applyMiddleware(),接受中間件函數做爲參數。中間件是用附加的鏈式功能重寫 store 的 dispatch() 函數的機制。

Providers。 對於用來做爲 UI 程序的 「View」 框架,Redux 並不武斷控制。它能夠和 React 或者 Angular 或者其餘框架配合使用。在這個框架中,「View」 是 UI 程序。和 Flux 同樣,Redux 被設計爲非分形的,而且以 Store 做爲協調器。

用戶事件處理函數的聲明可能在也可能不在 rendering。 取決於當下的 Provider。

BEST

Famous Framework 引入了 Behavior-Event-State-Tree (BEST),它是一個 MVC 的變體,BEST 中 Controller 分紅了兩個單向元素:Behavior 和 Event。

組成部分:

  • State: 用類 JSON 結構的聲明來初始化 state
  • Tree: 一個組件的聲明性分級結構
  • Event: 在 Tree 上的事件監聽,它能改變 state
  • Behavior: 依賴 state 的 tree 的動態屬性

BEST diagram

特色:

多範例。 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 是分形框架。

MODEL-VIEW-UPDATE

也被稱爲 「The Elm Architecture」,Model-View-Update 和 Redux 很類似,主要由於後者是受這個框架啓發的。這是一個純函數的框架,由於它的主語言是 Elm,一個 Web 的函數式編程語言。

組成部分:

  • Model:一個定義狀態數據結構的類型
  • View:將狀態轉化爲視圖的純函數
  • Actions:定義經過郵件發送的用戶事件的類型
  • Update:一個純函數,將前一狀態和 action 轉變爲新的狀態

Model-View-Update diagram

特色:

處處都是分級結構。 以前的幾個框架只在 「View」 中有分級結構,可是在 MVU 架構中這樣的結構在 Model 和 Update 中也能找到。甚至是 Actions 可能也嵌套了 Actions。

組件分塊導出。 由於哪裏都是分級結構,在 Elm 架構中的 「component」 是一個元組,包括了:模塊類型,一個初始模塊實例,一個 View 函數,一個 Action 類型,一個 Update 函數。縱覽整個架構,不可能有組件從這個結構中偏離。每一個組件都是 UI 程序,而且這個架構是分形的。

MODEL-VIEW-INTENT

Model-View-Intent 是基於框架 Cycle.js 的主要架構模式,它同時也是基於觀察者 RxJS 的徹底反應單向架構。可觀察(Observable) 事件流是一個全部地方都用到的原函數,Observables 上的函數是架構的一部分。

組成部分:

  • Intent:來自 Observable 用戶事件的函數,用來觀察 「actions」
  • Model:來自 Observable 的 actions 的函數,觀察 state
  • View:來自 Observable 的 state 的函數,觀察 rendering 視圖
  • Custom element:rendering 視圖的子部件,其自身也是一個 UI 程序。可能會做爲 MVI 或者一個 Web 組件被應用。是否應用於 View 是可選的。

Model-View-Intent diagram

特色:

極大的依賴於 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

這篇博文將 Nested Dialogues 做爲一個新的單向架構來介紹,適用於 Cycle.js 和其餘徹底依賴於 Observables 的方法。這是 Model-View-Intent 架構的一次進化。

從 Model-View-Intent 序列能夠函數化組合爲一個函數這個特性提及,一個 「Dialogue」:

A Dialogue function equivalent to Model-View-Intent

如圖所示,一個 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 重組後的樣子:

A Dialogue function as a UI program

要想將 Dialogue 方法做爲一個更大程序的 UI 程序子組件重複使用,這就涉及到 Dialogue 之間的嵌套問題:

Nested Dialogues

Observables 在 Dialogues 不一樣層之間的鏈接是一個數據流圖。它並沒必要須是一個非週期圖。在例如子組件動態列表這樣的實例中,數據流圖就必須是週期的。這樣的例子超出了本文的討論範圍。

嵌套的 Dialogues 其實是一個元架構:它對組件的內部結構沒有約束,這就容許咱們將前文所述的全部架構嵌入一個嵌套的 Dialogue 組件中。惟一的約束涉及 Dialogue 的一端的接口:輸入和輸出都必須是一個或一組 Observable。若是一個結構如同 Flux 或者 Model-View-Update 的 UI 程序可以讓它的輸入和輸出都以 Observables 呈現,那麼這個 UI 程序就可以做爲一個 Dialogues 函數嵌入一個嵌套的 Dialogues。

所以,這個架構是分形的(僅涉及 Dialogue 接口時)、通常性的。

能夠查看 TodoMVC implementationthis 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 僅僅是我如今的最強烈推薦。


Comments in Hacker News.

若是你喜歡這篇文章,分享給你的 followers:(tweeting)


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索