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