在本系列的開始部分,我們將看到像 React.js 這樣的 UI 框架是如何引入一種替代方法,來取代使用 MVC 作為設(shè)計(jì) Web 應(yīng)用程序和 UI 的主要方法的。請繼續(xù)閱讀,了解更多信息。
Js、 Elm、 Cycle.js 和其他 UI 框架引入了一種構(gòu)建用戶界面的新方法。從函數(shù)式反應(yīng)型編程到用戶界面開發(fā),它們甚至改變了我們對用戶界面的看法。這些方法很快就打破了 MVC 及其兄弟(MVP、 MVVM 等)看似不可避免的統(tǒng)治地位。本文是系列文章的第一篇,將簡要介紹這種構(gòu)建 UI 的新方法,并列出它與傳統(tǒng)方法相比的一些優(yōu)點(diǎn)。這些因素是如此強(qiáng)大,以至于在我看來,我們現(xiàn)在很有可能正在見證 MVC 時(shí)代的終結(jié)。
功能性反應(yīng)用戶界面開發(fā)的概念
從表面上看,像 React.js 這樣帶有 Redux 架構(gòu)、 Elm 和 Cycle.js 的框架似乎完全不同。Redux 應(yīng)用程序最初看起來類似于普通的 JavaScript 應(yīng)用程序,可能主要關(guān)注函數(shù)式編程。Elm 應(yīng)用程序有自己的語言,而 Cycle.js 應(yīng)用程序只由反應(yīng)流組成,這些反應(yīng)流以驚人的方式結(jié)合在一起。
但是在表面之下,所有這些框架都有一個(gè)共同點(diǎn): 功能性反應(yīng)式 UI 開發(fā)的本質(zhì)。
上面的圖片大致概述了這些概念,它們在幾乎所有培養(yǎng)響應(yīng)式編程的現(xiàn)代用戶界面框架之間共享。首先要注意的是,所有的事情——所有的變化、事件和更新——都朝著一個(gè)方向流動(dòng),形成一個(gè)循環(huán)。這篇文章將給出一個(gè)簡短的周期解釋,而后面的文章將進(jìn)入更多的細(xì)節(jié)。
函數(shù)式反應(yīng)式 UI 開發(fā)
這個(gè)循環(huán)由四個(gè)數(shù)據(jù)結(jié)構(gòu)(State、 Virtual DOM、 Event 和 Action)和四個(gè)組件(View ()-Function、 DOM-Driver、 ActionCreator 和 Updater)組成。DOM-Driver 由框架提供,而其他組件必須由應(yīng)用程序開發(fā)人員實(shí)現(xiàn)。
假設(shè)我們的應(yīng)用程序 todo-list 已經(jīng)運(yùn)行了一段時(shí)間,用戶按下按鈕在 todo-list 中創(chuàng)建一個(gè)新條目。這將導(dǎo)致 DOM 中的按鈕單擊事件,DOM-Driver 捕獲該事件并將其轉(zhuǎn)發(fā)給我們的 ActionCreators 之一。
ActionCreator 獲取 DOM 事件并將其映射到操作。操作是命令模式的一個(gè)實(shí)現(xiàn),也就是說,它們描述了應(yīng)該做什么,但是它們本身不修改任何東西。在我們的示例中,我們創(chuàng)建一個(gè) AddToDoItemAction 并將其傳遞給 Updater。
Updater 包含應(yīng)用程序邏輯。它保持對應(yīng)用程序當(dāng)前狀態(tài)的引用。每次它從 ActionCreators 之一接收到一個(gè)操作時(shí),都會(huì)生成新的狀態(tài)。在我們的示例中,如果當(dāng)前狀態(tài)包含三個(gè) todo-item 并且我們收到 AddToDoItemAction,Updater 將創(chuàng)建一個(gè)新狀態(tài),其中包含現(xiàn)有 todo-item 和一個(gè)新狀態(tài)。
狀態(tài)被傳遞給 View ()-Function,它創(chuàng)建所謂的 VirtualDOM。顧名思義,Virtual DOM 并不是真正的 DOM,而是一種描述 DOM 應(yīng)該是什么樣子的數(shù)據(jù)結(jié)構(gòu)。上面的代碼片段顯示了一個(gè)簡單的 的 Virtual DOM 示例。稍后的文章將詳細(xì)解釋 VirtualDOM 及其優(yōu)點(diǎn)。
VirtualDOM 被傳遞給 DOM-Driver,后者將更新 DOM 并等待下一個(gè)用戶輸入。有了這個(gè),這個(gè)循環(huán)就結(jié)束了。
好處
功能性反應(yīng)式 UI 開發(fā)相對于傳統(tǒng)方法有三個(gè)主要的優(yōu)勢,它們都是巨大的優(yōu)勢: 直接的測試、全面的事件流和時(shí)間旅行(是的,真的)。
簡單的測試
View ()-Function 和 ActionCreators 是簡單的映射,而 Updater 對它接收到的 Actions 執(zhí)行折疊(通常也稱為 reduce)。
所有組件都是純函數(shù),純函數(shù)非常容易測試。
純函數(shù)的結(jié)果只取決于輸入?yún)?shù),它們沒有任何副作用。要測試一個(gè)純函數(shù),只需創(chuàng)建輸入?yún)?shù)、運(yùn)行“測試中的函數(shù)”并比較結(jié)果即可。沒有樣機(jī),沒有依賴注入,沒有復(fù)雜的設(shè)置,沒有其他技術(shù)是必要的,沒有樂趣的測試。
綜合事件流
響應(yīng)式編程有很多樂趣——除非它不是。圖形用戶界面的控制流本質(zhì)上是基于事件的。應(yīng)用程序必須對來自用戶或服務(wù)器的按鈕單擊、鍵盤輸入和其他事件作出反應(yīng)。應(yīng)用反應(yīng)技術(shù),無論是觀察者模式、數(shù)據(jù)綁定還是反應(yīng)流,都是自然而然的。
不幸的是,這些技術(shù)都是有代價(jià)的。如果組件 A 調(diào)用組件 B,則很容易在 IDE 或調(diào)試器中查看連接。但是,如果兩個(gè)組件通過事件連接起來,那么這種關(guān)系就不那么明顯了。應(yīng)用程序變得越大,就越難理解其內(nèi)部結(jié)構(gòu)。
功能性反應(yīng)應(yīng)用程序的體系結(jié)構(gòu)通過定義所有組件都必須遵循的簡單事件流來避免這些問題。
無論應(yīng)用程序的規(guī)模有多大,事件流永遠(yuǎn)不會(huì)改變。
時(shí)間旅行
功能性反應(yīng)式應(yīng)用程序允許您在時(shí)間上來回旅行——至少在應(yīng)用程序的上下文中是如此。如果我們存儲初始狀態(tài)和所有操作,我們可以使用一種稱為“事件采購”的技術(shù)。通過重播操作,我們可以重新計(jì)算應(yīng)用程序所處的每個(gè)狀態(tài)。如果我們只重播最后的 n-1,n-2,n-3… 動(dòng)作,我們實(shí)際上可以回到過去。通過修改記錄的行動(dòng)流,同時(shí)應(yīng)用它們,我們甚至可以改變過去。正如您可以想象的那樣,這在開發(fā)和修復(fù)錯(cuò)誤時(shí)非常方便。
第一個(gè)時(shí)間旅行調(diào)試器已經(jīng)建立,但我認(rèn)為我們才剛剛開始了解的可能性,更驚人的工具將在未來發(fā)布。
摘要
到目前為止,我們只觸及了功能性反應(yīng)式 UI 開發(fā)的表面,但是到目前為止,應(yīng)該很清楚這種方法具有一些巨大的優(yōu)勢。以后的文章將更深入地討論技術(shù)細(xì)節(jié),但也會(huì)展示其缺點(diǎn)(或者我們稱之為“尚未解決的挑戰(zhàn)”) ,并展示如何將所學(xué)到的經(jīng)驗(yàn)教訓(xùn)應(yīng)用于 JavaFX 應(yīng)用程序的示例。