做者 | Harshal Patil譯者 | 王強編輯 | 張之棟、王文婧早期的軟件架構模式創建在有限的硬件功能上並尊重這一事實,然而,今天的狀況已經變了。計算能力在不斷提高,而軟件架構印證了這種觀點。本文從經典MVC提及,詳盡解讀了當代前端架構及下一階段的展望。對於前端工程師,以及想從宏觀層面理解現代Web應用程序架構的Web開發人員來講,均能從中獲益。javascript
軟件架構的核心思想,就是推斷軟件系統各個組件之間數據流動的方式。html
軟件架構的質量取決於你設法推斷這些數據流的難易程度!前端
本文要講的內容,就是在今天的 Web 應用程序背後探索這些數據流和最終的體系結構。Web 應用已從簡單的靜態網站(雙層結構)發展爲複雜的多層次、SPA 和 *** 驅動的 API 優先系統。CMS 系統已發展成無頭(Headless)和內容優先的系統。java
近年來,前端社區的面貌突飛猛進。早年流行的是 jQuery 引入的 DOM 注入算法,其很快被基於 MVC 的 Backbone.js 所取代。以後一晚上之間,咱們就身陷於雙向和單向數據流架構的叢林之中。咱們的成長足跡在某處中斷了。曾幾什麼時候沉浸在 MVC 的世界是如何忽然進入 React 開創的單向數據流時代的?它們之間有什麼聯繫?隨着本文的展開,咱們將嘗試解開這個難題。node
雖然本文針對的是前端工程師,但想要從宏觀層面理解現代 Web 應用程序架構的 Web 開發人員都能從本文中獲益。軟件體系背後有着大量活動——流程、需求收集、部署拓撲、技術棧等等,但那些已經超出了本文所涉及的範圍。react
必要的前置知識——什麼是計算機?計算機是一種從用戶收集數據 / 信息,並馬上或稍後將處理過的數據 / 信息提供給用戶的機器。計算機如何收集和展現這些數據呢?它使用軟件應用來實現這一目的。web
軟件架構的關鍵是提供合理的手段來組成軟件,同時保持一切井井有理。算法
這裏的關鍵在於,軟件應用正在處理的數據被稱爲模型或應用程序狀態。一些勇士可能將其稱爲應用程序的域模型或業務邏輯。應用程序能夠是桌面端也能夠是 Web 端。編程
本文的宗旨是探索向(Web 前端的)用戶表示這種應用程序狀態的合理方式,同時讓一切井井有理。咱們將探索數據如何從模型流向視圖層,僅此而已。redux
經典 MVC——起源將數據與表示分離是(Web 端和桌面端)圖形用戶界面的核心思想。對於 MVC——模型 - 視圖 - 控制器來講,將表示(視圖)與關注域(模型)分離是其主導的設計理念。毫無疑問,MVC 是一項開創性的成果,其影響力綿遠流長。
MVC 是爲 Smalltalk-80 語言推出的。在 MVC 中,視圖(View)對象顯示模型(Model)對象持有的數據。在咱們全面研究 MVC 中的數據流以前,咱們必須瞭解當時(約 20 世紀 70 年代)的軟件應用環境:若是要爲軟件開發寫出第一原則,那麼它就會是 SoC——關注點分離。並且 MVC 模式多是它第一個真正的落地實踐。
這意味着應用軟件很是接近底層硬件。
以上這些限制對 MVC 影響很大。因而須要由控制器(Controller)對象負責響應鍵盤或鼠標等用戶輸入並轉換爲模型上的操做。此外,操做系統缺乏 GUI 小部件意味着視圖與屏幕內容沒法對應。
相反,視圖和控制器以配對的形式結合在一塊兒。其中視圖部分向用戶顯示輸出內容,控制器部分接收來自用戶的輸入。應該注意的是,屏幕上的每一個控件都有一個視圖——控制器配對,這也是小部件(widget)理念的雛形。
今天,在 React、Vue 或 Angular 中,這種視圖——控制器配對與組件的思想是同樣的,儘管狀態處理層面的具體機制有所不一樣。
關於 MVC 就介紹到這裏,下面的圖片展現了 MVC 中的數據流示例。在這個例子中,咱們有一個帶遞增和遞減按鈕的簡單計數器。計數器狀態維持着模型。此外,咱們把兩個按鈕簡化成了一個來簡化敘述。
經典 MVC
在協做方面:模型實現了觀察者(Observer)模式,被一個或多個視圖對象註冊(subscribe)。當模型更改時會觸發事件,事件響應後視圖也會更新。
MVC 中有兩條數據流。在視圖流中不涉及模型,只涉及 UI 的更改。顯示按鈕單擊效果或對鼠標滾動事件做出響應就是視圖流的例子。
MVC中的數據週期
人們很快就意識到應用程序狀態沒法與 GUI 徹底隔離。咱們老是要維護某種表示邏輯或視圖狀態。
複雜的圖形界面須要額外的狀態,這些狀態只用來輔助 UI 小部件,實現更好的用戶體驗。
但這可能會致使一些問題。來看看以前的計數器示例。當計數器達到 10 時,咱們必須將標籤的顏色從黑色更改成紅色以表示警告。這種顏色變化行爲實際上不是業務邏輯或關注點。這是純粹的美學部分(用戶體驗),須要加進來。真正的問題是——放在哪裏?是模型仍是視圖?
因爲這種表示邏輯或視圖狀態基本上是從域模型派生的狀態,所以必須在模型對象中維護它。可是域模型又要維護視覺部分(好比說紅色),這種定義就很尷尬。若是咱們將它放在視圖對象中,那麼它會引入另外一組問題。咱們的標籤小部件再也不是通用的了,沒法在其餘地方複用。此外,在視圖對象中放一個帶有硬編碼數字 10 的條件,意味着咱們正在泄漏某些業務邏輯。
爲了解決這個問題,咱們在原始的 MVC 中添加了另外一個實體——應用程序模型(Application Model,AM)。有了 AM 之後,視圖——控制器配對再也不直接訪問模型了。相反,它們向 AM 事件註冊並使用它來訪問所需的數據。
應用程序模型MVC
數據流和經典 MVC 是同樣的。固然,每種模式都有其優勢和缺點,AM-MVC 也不例外。最突出的問題是AM沒有對視圖對象的直接引用,所以即便 AM 被設計爲維持視圖狀態,也沒法直接操做後者。
總的來講,引入應用程序模型會使視圖特定的狀態遠離域層,並下降了視圖對象的複雜性來簡化視圖對象。這和表示模型(Presentation Model)很像,這是 Martin Fowler 在其開創性研究中創造的概念:
現代桌面架構的時代表示模型的本質是一個徹底自包含的類,它表示 UI 窗口的全部數據和行爲,但沒有任何用於在屏幕上渲染該 UI 的控件。視圖只是將表示模型的狀態顯示在屏幕上。
時光飛逝,世界也在不斷變化,新一代操做系統開始展示威力。應用程序遠離了底層硬件,它們之間加入了完整的內核、OS 驅動程序和實用程序。像 Windows 這樣基於 GUI 的操做系統提供了開箱即用的 UI 小部件。
再也不須要控制器來監聽輸入設備了。視圖對象的理念改變了。
控制器的大多數功能都由操做系統接手。視圖的理念發生了變化:以前它只是一個小部件,如今它是一個衆多小部件的組合;一個視圖能夠包含另外一個視圖。視圖在某種意義上變成了雙向的,它響應用戶操做並顯示模型數據。
前端世界中視圖的理念與這個概念很是類似。在 Web 環境中,視圖是一個完整的頁面。
經典 MVC 變得過期且難用。爲了適應這些不斷變化的環境,Dolphin 團隊在 1995 年正在尋找一種建立用戶界面的新模型。Dolphin 團隊如何找出新設計的歷史可參閱如下記錄,本文再也不贅述。http://aspiringcraftsman.com/2007/08/25/interactive-application-architecture/
在協做方面:總而言之,該團隊最終將 MVC 模型旋轉了 60 度,他們稱其爲 Twisting the triad。因而咱們有了 MVP。
根據實現,視圖註冊到模型上,並依賴表示器處理複雜邏輯;或者在其餘狀況下,視圖只依賴表示器處理一切。
正如 Martin Fowler 在關於 GUI 架構的論文中總結的那樣,他將 MVP 實現分爲監督控制器 MVP 和被動視圖 MVP。從圖中能夠看出它們的差別和各自的數據流。
MVP——模型視圖表示器
MVVM——模型 - 視圖 - 視圖模型MVP 很棒,有許多可能的變種和複雜的細節。但從前端應用程序的角度來看,MVVM 確實更勝一籌。在一些實現中,後者也被稱爲模型 - 視圖 - 綁定器。MVVM 很像被動視圖 MVP,但增長了數據綁定的功能。它是一種編程技術,未來自提供者和消費者的數據源綁定在一塊兒並同步。它擺脫了咱們過去須要用來保持視圖和模型同步的許多樣板代碼。這樣咱們就能在高得多的抽象級別上工做了。在協做方面:MVVM 有額外的綁定器(Binder) 實體,負責使視圖與視圖模型保持同步。每次視圖模型上的屬性更改時,視圖都會自動更新以反映 UI 上的更改。
MVVM——Model View ViewModel
MVVM 中的數據綁定已經成爲許多前端庫的基礎,包括 Knockout、Angular、Vue.js 和 React 等。
咱們將在 Web 應用程序部分再討論數據綁定。
進入 Web 應用程序領域就像原始的 MVC 模式同樣,出現了一種用於 Web 應用程序的模式。它被稱爲 Web MVC。實際上,Web 應用程序的構建和部署方式讓 MVC 在 Web 端比在桌面端更加順其天然。
社區的主要困惑是不知道桌面 MVC 和 Web MVC 是兩種不一樣的模式。要是 Web MVC 當初不叫這個名字,你們就會清楚多了。
Web 應用程序是分佈式應用程序的子類別。雖然 MVC 對 Web 應用程序感受更天然一些,但通常來講構建分佈式應用程序是很困難的。代碼的某些部分在服務器上運行,同時還要保留在客戶端(例如瀏覽器)上。
在 MVC 的語境中討論 Web 應用程序時有三個不一樣的數據週期,所以有三個 MVC 實現:服務端 MVC、瀏覽器內部的 MVC 和前端 MVC。瀏覽器負責三種類型交互的中介:大規模 Web 應用程序架構的重點在於肯定代碼的哪一個部分應該在哪裏執行。咱們有服務端驅動的應用程序或富客戶端驅動的應用程序。在二者之間,咱們能夠無限自由組合。
在用戶和客戶端代碼之間。
瀏覽器有本身的模型、視圖和控制器。做爲開發人員,咱們沒必要擔憂瀏覽器 MVC。
服務端 MVC,亦即 2 號模型服務端 MVC 的第一個衆所周知的實現是 Sun Microsystems 針對 Java Web 應用程序的 2 號模型(Model 2)。
服務端 MVC——前端視角
這個 MVC 與傳統的 MVC 很是類似,但因爲數據跨越客戶端和服務端邊界時數據流週期時間呈指數級上升,所以前者多出來不少複雜性。須要注意的一些事項有:前端控制器(Front Controller):是一般由底層技術棧提供的組件,用於處理 HTTP 請求調度。例如 Java Web 應用程序中的 Servlet 容器、ASP.NET 中的 IHttpHandler 或 Node.js 中的 HTTP.Server 類:
https://nodejs.org/api/http.html#http_class_http_server
今天的 ***——服務端渲染則是徹底不一樣的概念。然而事實並不是如此。因爲整個 HTML/ 內容是由服務端生成的,而且不涉及客戶端 JavaScript 代碼,所以徹底使用服務端 MVC 構建的 Web 應用程序仍被視爲 ***。
超越服務端從這裏開始就有意思了。不知不覺中,幾乎全部瀏覽器都開始提供 JavaScript 引擎。在我看來正是 AJAX 改變了 Web 應用程序。谷歌是最先的實踐者,在其郵件客戶端和地圖應用中採用了這項技術。
這個世界由服務端 MVC 生成 HTML+JS。JS 代碼遍及全部頁面。JavaScript 主要用來減小服務端視圖週期以改善用戶體驗。表單提交、輸入驗證等內容由客戶端代碼處理。
它是 Web 應用程序歷史上最流行的架構。大多數 B2C 應用程序、SEO 友好網站,尤爲是使用 CMS(內容管理系統)構建的網站都在使用它。客戶端代碼量取決於應用程序的需求。
這樣的架構從未真正標準化,所以沒有名稱。它一直在漸進發展,仍被認爲是 Web MVC 的擴展。ASP.NET MVC、Java Struts、Python Django、Ruby ROR 和 PHP CodeIgniter 是流行框架的一些例子,它們大量使用服務端 MVC 或 Web MVC。
固然,這種標準模式還有不少變體,但它們對當代前端架構沒有任何實際影響,能夠忽略。
基礎 RIA——富互聯網應用架構瞭解過這些背景,咱們如今準備討論當代前端架構。當代前端架構主要解決的是構建 RIA——富互聯網應用程序的問題。RIA 的確切定義並不存在,由於不少事物都能用它描述。但總的來講,RIA 或富 Web 應用是應用程序的一個類別,其中應用程序嚴重依賴客戶端代碼,而且它們的用戶體驗很是接近桌面應用程序。它主要使用支持 SPA(單頁面應用程序)的框架構建。來自 Web MVC 的 服務端視圖週期的數據流這裏不存在。它一般只有一個初始 HTML 頁面,而後使用客戶端代碼和路由解決方案來渲染後續頁面。
構建 RIA 是一項複雜的操做,這種操做從之前基於桌面的 GUI 架構學習發展而來。視圖模型、觀察者、組件等就是從這些架構中借用的一些理念。Oliver steel 在他 15 年前的博客文章中(相信我,它是最出色的文章之一),爲理解 RIA 數據流提供了很好的參考架構: https://blog.osteele.com/2004/08/web-mvc/
客戶端變重了——SPA 的時代
RIA 參考架構和 Web MVC 之間最顯著的區別是前者的頂層架構中缺乏控制器和視圖。然而字面意義上講它們並無消失。若是咱們看一下底層,控制器和視圖仍然存在,只是承擔的角色大大縮減了。後端主要是 API 驅動的。視圖僅限於生成 JSON,控制器負責編排傳入請求並將其映射到適當的業務工做流。
GUI 模式很難?若是你已經深刻探索了前面的模式,那麼你將意識到 GUI 模式與其餘軟件工程模式有所不一樣。以可複用的面向對象軟件的元素爲例:大多數模式都獨立於技術或語言,但 GUI 模式並不是如此。這些模式適用於人機交互的邊界。用戶和反作用本質上是模式的一部分。注意:可複用的面向對象軟件的元素已經指出了經典 MVC 的本質。
GUI 模式適用於 HCI——人機交互的邊界。用戶和反作用本質上是模式的一部分。
所以,在不考慮基礎框架或語言的狀況下幾乎不可能在理論上討論它們。到目前爲止,咱們能夠在至關高的抽象級別上探索這些模式。但當咱們接近本文的核心時,咱們將依靠不一樣的庫或框架來描述這些模式。
大多數 Web UI 模式能夠分爲三個階段,即進化、變革和當代階段。
前端圖譜——宏觀視角
進化模式只是服務端 MVC 的擴展。它們並不會試圖改變方向或發明全新的事物。它們會一步一步地改進,以補足現有應用程序的缺陷。而變革模式是將前端應用開發與服務端驅動的工做流程分離的那些理念。它們的到來標誌着 SPA 應用的興起。當代模式就是對這些變革模式的二次修正,也是前端社區前進的大方向。
DOM 注入算法由 jQuery 引入和掌握的這項技術是編寫大規模客戶端應用程序的真正起步,儘管 jQuery 並未真正解決架構問題。它旨在簡化當瀏覽器中存在太多不一致時的 DOM 操做。它提供了與瀏覽器無關的 API。
雖然我不以爲這是有意爲之,但 jQuery 把 DOM API 簡化到了很難將其與正常的語言 API 區分的程度。這反過來又讓開發人員將 DOM API(視圖層)與業務邏輯(模型)完美地混合在一塊兒。
須要指出的一點是,它仍然在同一服務端 MVC 的上下文中。這只是一個擴展。沒有真正的控制反轉。視圖 / 頁面的整體控制仍然由服務端代碼驅動。
jQuery——HTML 代碼
jQuery——DOM 注入算法
在上面的代碼片斷中,模型、視圖和表示器 / 控制器被混合成一個單體代碼結構。當模型只包含一個屬性時就是這種狀況。想象一下,嘗試構建一個沒有服務端視圖週期的 Web 應用程序(好比 SPA),作這樣的事情是沒有意義的。與 DOM 交互的代碼簡潔地注入了其餘應用程序邏輯,這就是爲何它被稱爲 DOM 注入算法(注意:我不肯定 DOM 注入這個術語是否是什麼規範名稱)
Backbone.js——MV*正如咱們在 jQuery 中看到的那樣,在開發應用程序時顯然缺乏構建和組織代碼的方法。因而 Backbone.js 應運而生,成爲了新的進化解決方案。它是首批將 MVC 風格帶給客戶端的庫之一。
Backbone.js 數據流
咱們看一看 Backbone 數據流示意圖,就能夠清楚地看到模型和視圖,可是沒有控制器的等效對象。模式在不斷髮展,客戶端 MVC 只是以前 MVC 架構的進化。在這個進化過程當中,大多數 JavaScript 社區都贊成模型和視圖的定義,但針對控制器沒有造成共識。考慮到客戶端環境 ,控制器的理念並不合適。控制器留待進一步解釋。
至於 Backbone 而言,控制器不存在。那麼它適合哪裏?是 MVC、MVP 仍是 MVVM?借用服務端 MVC 的定義,控制器有兩個職責:以傳入的 HTTP 請求的形式響應用戶操做,並協調模型以生成視圖(HTML 頁面)。在 Backbone 的狀況下,視圖和路由器共享這些職責,可是缺乏獨立的控制器或表示器的概念。
一些開發人員認爲 Backbone 是 MVP,其中視圖相似於表示器,模板(Template)是視圖,而 Backbone模型和集合(Collection)構成模型。
正如 Addy Osmani 在他的博客中所說,最好不要強行把 Backbone 歸類到任何特定的設計模式。設計模式應被視爲應用程序結構的靈活指南,由此看來 Backbone 既不像 MVC 也不像 MVP。相反,它借鑑了多種架構模式中的一些最佳概念,並建立了一個運行良好的靈活框架。
這就是 MV* 或 模型 - 視圖 - 其餘 的誕生歷程。強烈推薦 Addy Osmani 的博客文章:理解 MVC 和 MVP——適合 JavaScript 和 Backbone 開發人員。https://addyosmani.com/blog/understanding-mvc-and-mvp-for-javascript-and-backbone-developers/?source=post_page-----fb5b500b0231----------------------
與以前的 jQuery 相比,Backbone 能夠生成更加結構化的代碼。
Backbone.js 中的視圖模板
在 Backbone.js 中建立一個模型
在 Backbone.js 中建立一個視圖實例
在 Backbone.js 中連接視圖和模型
前文中我將 Backbone 稱爲下一個進化解決方案。緣由是它只是補充並擴展了服務端 MVC。例如,若是咱們的後端是 RESTful,意味着前端代碼只是用來表示服務端模型的手段,那麼 Backbone 已預置爲與 API 同步:
Backbone.js 中自動生成的集合方法
並且 Backbone 中還有許多其餘小型約定,感受只是擴展而已。總之,Backbone 可能不是當時惟一的解決方案,但它在代碼結構和組織領域確實作出了開創性的工做。像 jQuery 同樣,它被許多產品採用。
Knockout.js——前端的數據綁定 Knockout.js 是咱們基本模式的最後一個案例。它旨在爲 JavaScript 實現 MVVM——模型 - 視圖 - 視圖模型。它也確實作到了。Backbone 解決的是代碼組織和結構的問題,而 Knockout 是在 聲明式數據綁定的幫助下有效地實現 視圖層。聲明式綁定的優勢與其餘聲明式編程結構相同:可觀察:Knockout 在事件之上提供更高級別的抽象。這容許 Knockout 自動跟蹤視圖模型props 之間的依賴項。若是須要,咱們能夠註冊可觀察屬性。
Knockout.js 視圖——雙向綁定
Knockout.js 中的視圖模型——使用計算屬性響應
雖然 Knockout 爲視圖和視圖模型提供了定義良好的結構,但卻沒關注應用程序模型的問題。這使得 Knockout 高度專一、能夠普遍適應,由於它能夠用做庫而非框架。我見過有人用它構建迷你 SPA 應用程序,其中 Web 應用程序有多個頁面,每一個頁面都是一個小型的 Knockout 應用。這個StackOverflow 答案清楚地定義了 Knockout 的 MVVM 實現的範圍: https://stackoverflow.com/a/14516490/5723098
一般假設 Knockout 模型駐留在服務端。視圖模型只是使用 Ajax 或等效方式詢問服務端模型。
它在 DOM 更新用途上取代了 jQuery 和像 Handlebars 這樣的模板解決方案,但仍然使用 jQuery 來製做動畫、Ajax 等實用程序。與 Backbone 結合後,它能夠做爲 MVVM 模式的完整實現。理論上講這可能已成事實,但在此以前,這些概念已經被下一代工具鏈吸納過去了。
接下來 Angular 一、Aurelia、Ember.js 等表明的變革旅程開始了。
因爲它與.NET 世界緊密相關,所以被普遍用於 ASP.NET MVC 應用程序中。像 Backbone 同樣,它是另外一種進化解決方案,針對的是稍微不同的問題。並且客戶端代碼依舊只是服務端 MVC 模式的擴展。服務端仍然是主導架構。彼時 Backbone 和 Knockout 常被拿來對比,但實際上它們是免費的解決方案。
Knockout 和 Backbone 都是 JavaScript 庫。不知何故,Backbone 被視爲一個框架。爲何?這裏沒有肯定的答案,但多是由於視角不一樣。因爲強調代碼結構,Backbone 始終採用更高級別的抽象處理。此外,Backbone 從沒想過要取代無處不在的 jQuery(即便在 2019 年,最流行的 100 萬個網站中有七成都在使用 jQuery),而 Knockout 與核心 jQuery 功能高度重合(好比 DOM 操做),這天然讓 Knockout 舉步維艱。所以與 Backbone 相比,Knockout 的應用被大大侷限了。但它仍然是前端社區首批實現的聲明式數據綁定之一,值得在這裏專門提到它。
Angular 1——給我控制權jQuery 對 DOM 意味着什麼,Angular 1 就對通常意義的前端生態系統有着一樣的影響。它永久改變了構建大規模客戶端應用程序的概念。做爲一個框架,它引入了許多概念——模塊系統、依賴注入、控制反轉和更簡單的數據綁定等。
曾幾什麼時候,選擇正確的 JavaScript 庫,併爲前端構建完美的技術棧仍然是一件痛苦的事。Angular 1 提供了一種更簡單但有凝聚力的替代方案。Ember.js 和其餘相似的框架也能夠這麼說,可是 Angular 1 的普及度是徹底不一樣的層面,稱得上是那個時代的選擇。
是框架仍是庫?先前的解決方案更像是庫而不是框架。毫無疑問,Angular 1 是一個明肯定義的框架。框架和庫之間的關鍵區別是 IOC——控制反轉。做爲框架,Angular 1 具有:從某種意義上說 Angular 1 是變革解決方案,它再也不是對服務端 MVC 的簡單擴展,而是在頁面上灑滿了客戶端代碼。Angular 1 使 SPA 成爲構建下一代用戶體驗的幾乎是事實上的一流解決方案。
模塊系統:Angular 1 引入了特定於框架的模塊系統。模塊是幾乎全部語言的代碼組織的基礎。JavaScript 直到 2015 年纔有模塊系統(瀏覽器直到 2018 年才支持它)。Angular 在組織方面領先時代。
使人困惑的術語:Angular 1 使用的一些術語顯然使人困惑。例如,Angular 1 用 $scope 做爲視圖模型,但它也有控制器能夠擴充 $scope 對象。可能正確的名稱能夠是 VMFactory 之類。此外,Angular 1 有三種服務配方,其中一種名爲 Service。
還有許多其餘小問題。另外,Angular 2 或簡稱 Angular 是一個完全的更新,它就像一個全新的框架。除了名字和少許概念以外,兩代版本之間找不到什麼共同點。
Angular.js——概覽
多年來,Angular 1 發佈了一些小版本更新,修復了許多複雜的小細節。最重要的是增長了組件模型,組件模型是前端世界大多數理念的交匯點。
Angular 1 在前端社區的遺產影響深遠。它的優缺點幫助社區瞭解了軟件架構的重要性,併爲編寫可擴展應用程序的工做提供了基準。它的缺點 / 不足成爲了將來架構解決問題的基礎。
當代前端架構現代應用程序在各個層面都很是接近桌面應用程序。當今環境中的一些顯著變化包括:
許多新的框架和工具使用 JavaScript 做爲目標語言,而非將其用做源語言。一些不錯的例子有 Elm、PureScript 和 ReasonML 等等。
當代前端架構是這些不斷變化的需求的反映。顧名思義,它們創建在整個前端社區從過去學到的經驗之上。
早期的軟件架構模式創建在有限的硬件功能上並尊重這一事實。今天的狀況已經變了。計算能力在不斷提高,並且軟件架構反映了這種觀點。
以上假設能夠與今天的前端架構相關聯。這些架構融合了三大核心原則。
數據流佔據舞臺中心考慮到前端代碼須要運行的領域衆多、環境多樣,業務邏輯佔據了中心位置。做爲工程師,咱們花費更多時間來閱讀和調試,而非編寫代碼。咱們必須直觀地跟蹤代碼。每當修復錯誤或向現有代碼庫添加新功能時,瞭解其可能致使的影響和迴歸是相當重要的。
在大型代碼庫中作到這一點的惟一方法是確保 咱們瞭解數據在應用程序中的流動方式。這是軟件架構最重要的目標。
今天,任何框架都是圍繞這條關鍵原則構建的。明確區分狀態和視圖是很是重要的——鑑於簡單的基於事件的容器在向更復雜的狀態容器(如Redux、Vuex 和 Ngrx 等)轉變,這也是顯而易見的。因爲咱們嚴重依賴事件或發佈/訂閱系統,數據(或控制流)將流經應用程序的每一寸角落。對數據流的重視再也不侷限在局部環境。相反,做爲一種核心思想,如今咱們要在整個應用程序中考慮數據流。
Angular 1 已經證實雙向數據流(即時只侷限在視圖或組件上)能夠帶來紛繁複雜的控制流。此外,React 已經證實單向數據流很容易推斷,所以包括 Angular 2 在內的現代框架也遵循了這一理念。
基於組件的架構轉向基於組件的用戶界面是數據流第一原則的必然結果。在談論組件時,有三個方面須要考慮。
首先是數據流的重點:傳統架構側重於水平分工。可是基於數據流的思惟要求垂直分工。在敏捷世界中,這就是咱們設想用戶故事的方式。基於 MVC 的架構不容易作到這一點。沒有簡單的方法能夠經過一項功能共享或複用 UI 代碼(這裏的問題是——當功能在橫向組織推進下在整個代碼庫中傳播時,咱們怎樣才能真正隔離功能呢!)。可是,封裝入一個完整功能的組件能夠輕鬆配置爲可共享和可打包的實體。
其次是不斷髮展的視圖狀態:過去,視圖狀態與業務狀態相比佔比較少。但隨着應用程序變得更具交互性,其佔比也獲得了爆發式增加。視圖狀態須要接近實際視圖。視圖狀態很複雜,一般表示必需的時間關聯數據,如動畫和轉換等。相似 MVC 的架構沒有什麼很好的方法來封裝這種狀態。在這裏,做爲封裝核心單元的組件很是有用。
第三個方面與 UI 開發的原子單元有關。首先咱們提出一個問題:
共享 UI 功能的最佳方式是什麼?共享 UI 功能意味着它能夠包含如下四個部分:
結構(HTML—視圖)、樣式(CSS—視圖)、行爲(JavaScript—視圖模型)和業務邏輯(模型)。
因而組件的概念應運而生。咱們意識到組件只是 MVVM 模式 或MV* 模式 的一個很好的實現……
但具體而言,如何表示 UI 組件?我能找到的最接近的概述是 Deric Baily 的博客文章:
https://derickbailey.com/2015/08/26/building-a-component-based-web-ui-with-modern-javascript-frameworks/
在語言級別,一般使用模塊或包來表示可複用功能的構建塊。JavaScript 也有模塊。但這還不夠。
組件的這種抽象概念容許框架做者根據他們的須要定義具體的實現。
此外,良好的組件是高度可組合的,可實現分形架構。例如,登陸表單組件能夠是標準登陸頁面的一部分,或者在會話超時時顯示爲對話框。一個組件只要定義好接收的屬性和發送的事件,就能夠在知足基礎框架要求的前提下複用在任何地方。
組件能夠是由 Webpack 等打包器生成的函數、類、模塊或打包代碼。
如前所述,組件只是一個 MVVM 模式的良好實現。而且因爲組件的可組合性,咱們將 MVVM 模式實現爲分形(分形是一種自我重複的無止境模式)。這意味着咱們在多個抽象級別處理多個獨立的的 MVVM 控制流。
實現 MVVM 的無限組件分型——循環套着循環!是否是很像《盜夢空間》?
並且,這正是咱們對架構所指望的。
讓框架處理 DOMDOM 用起來既昂貴又繁瑣。當 狀態( 本地或 全局)發生變化時,DOM 能以某種方式自動更新就最好了。此外,它應儘量高效同時不干擾其餘元素。在將 DOM 與狀態同步時有些事情須要注意。一個好的架構能夠實現多級抽象。它容許咱們一次查看一個抽象(細節級別)而沒必要擔憂其餘級別。這是製做可測試和可讀解決方案的關鍵所在。
更新 DOM:在檢測到實際更改後,框架須要更新 DOM。許多框架(如 React、Vue 和 Preact 等)使用虛擬 DOM(對時間優化),而 Angular 使用增量 DOM(對內存優化)。
定義當代前端架構早期 GUI 架構的範圍主要集中在代碼結構和組織上。它主要關注模型與其表示之間的關係,也就是視圖。那是當時的需求。
今天狀況已經發生了變化。現代前端架構的需求已經遠不止簡單的代碼組織。在社區中,咱們已經把這些知識傳承了下來。
大多數現代框架已經在組件的幫助下標準化了代碼結構和組織概念。它們是當今架構的基本單元。
還應注意,架構與其實現框架密切相關。這能夠追溯到這樣一個事實,也就是 GUI 模式很複雜,而且因爲它直接對接用戶而沒法獨立於其實現來討論。咱們能夠繼續說一個框架就是它的架構。
設計良好的組件系統是任何前端架構或框架的最基本需求。除此以外,它必須解決前面討論的許多其餘問題,如聲明式 DOM 抽象、顯式數據流、更改檢測等。大多數元素都在下圖中高亮顯示:
現代前端架構元素
考慮到全部這些因素,咱們可能會想要建立一個參考架構,但這根本不可能。每一個框架或庫在實現時都有本身的庫特性。例如,React 和 Vue 都支持單向數據流,但 React 更喜歡不可變數據,而 Vue 則支持可變性。所以,最好針對單個框架來全面瞭解其架構。
話雖如此,咱們仍然能夠嘗試建立近似的參考架構,以下所示:
現代參考架構
到目前爲止咱們描述的全部特徵或元素都包含在這個架構中。它乾淨地映射到三層架構,但第二層的中間件是可選的。第二層表示 UI 服務器,理解的人很少。UI 服務器只是一個服務器,旨在爲現代重客戶端的 UI 應用程序提供服務。它負責正交領域,如 SSO 集成、身份驗證和受權,服務端渲染、會話管理、服務 UI 資產和緩存等。此外,當大規模應用程序在某處部署 API 服務器時,它充當反向代理以調用 API 避免 CORS 問題。這裏事實上的標準選擇是 Node.js,由於許多框架都在 Node 中編寫了 *** 渲染器,而 Node 因爲其異步特性而擅長處理大 I/O 請求。咱們將在另外一篇關於 Web 應用程序拓撲的文章中進一步討論 UI 服務器。
當代架構最重要的變化是模型的概念。
模型再也不被視爲一個黑盒子。它被分隔成應用程序範圍的全局狀態和組件範圍的本地狀態。全局狀態一般使用複雜的狀態容器(如 Redux、Mobx 和 Vuex 等)管理。每一個組件的本地狀態是三個事物的聯合——全局狀態切片、組件的私有本地狀態(異步數據、動畫數據和 UI 狀態等)和由父組件做爲 props 傳遞的最終狀態。咱們能夠將本地狀態視爲模型和視圖模型的更好抽象。當咱們將 GraphQL 添加到這個等式中時,狀態管理會發生變化。
數據從上到下,從父組件流向子組件時是單向的。雖然框架容許數據直接反方向流動,但不鼓勵這樣作。相反,事件是從子組件中觸發的。父組件能夠監聽或忽略它們。
不完整的藍圖這個參考架構並無真正捕捉到當代架構的所有本質。大多數 Web 流量由靜態網站和 CMS(內容管理系統)驅動。現代工具鏈已經大大改變了咱們開發和部署這些應用程序的方式。在 CMS 的狀況下,他們經過解耦前端與後端而變得 無頭(Headless)。Strapi 和 Contentful 等的崛起就是明證。與此同時,咱們再也不使用純 HTML 和 CSS 構建靜態網站。靜態站點構建器和生成器也變得很是流行。咱們如今能夠使用相同的前端框架來構建由複雜的構建工具輔助的靜態網站。使用 React.js時,咱們能夠使用 Gatsby.js 和 Vue.js,咱們有 Nuxt.js。當咱們編譯代碼時,它會生成一個靜態網站,能夠完整部署到任何靜態 Web 服務器。這種技術稱爲預渲染,與服務端渲染對應。
這裏咱們有了另外一個用於構建靜態網站的當代架構。這裏的思想是使用像 Strapi 同樣的無頭 CMS,並使用像 Gatsby 這樣的靜態網站構建器來構建前端。在構建中,當咱們生成靜態網站時,咱們從 CMS 中提取全部數據並生成頁面。
看成者更改無頭 CMS 中的內容時,咱們從新觸發咱們的靜態網站的構建,使用新內容構建網站並當即部署到服務器或 CDN。
構建靜態網站——現代方式
這個新的工做流程就像運行一個成熟的動態網站同樣好用,也沒有 CMS 的缺點,如複雜的部署和緩慢的加載時間等等...... 因爲靜態網站能夠直接部署到 CDN。咱們得以快速加載並改進緩存。咱們還擺脫了靜態網站的全部問題,如更新週期緩慢和缺少可複用性等。這裏引述 Nuxt.js 網站的願景——
咱們能夠進一步考慮使用 nuxt generate 並託管在 CDN 上的電子商務 Web 應用程序。每當產品缺貨或補充庫存時,咱們都會從新生成 Web 應用程序。但若是用戶在此期間瀏覽這個 Web 應用程序,由於有了對電子商務 API 的 API 調用,應用將保持最新狀態。無需再用服務器 + 緩存的多組實例!
進一步考慮單向架構的話,能夠經過多種方式實現它們。框架有本身的作事方式。一些不錯的例子包括:總而言之,現代前端解決方案構建在基於組件的單向架構之上。
BEST、Vuex 和 Ngrx 等。
Andre Staltz 在他的博客文章中很好地描述了這些模式:AndréStaltz——單向用戶界面架構:
https://staltz.com/unidirectional-user-interface-architectures.html?source=post_page-----fb5b500b0231----------------------
當代仍是現代???到目前爲止,咱們有意不用「現代」這個詞,而一直在說的是「當代」。今天的架構實踐僅僅是咱們舊有理念的進化。咱們社區試圖將新事物融入現有的生態系統,老是留在邊界內,不多打破常規。所以,「當代」這個詞更能準確地描述這種理念。
在定義「當代」時,咱們必須將全部鬆散的目標聯繫起來,必須將過去、如今和將來聯繫起來。我能夠想到三種可能的連接——將來——函數組件
將今天的組件與歷史上的 MV* 聯繫起來?到這裏事情應該都很清楚,但可能會出現一個問題,那就是這些模式如何與以前的模式聯繫起來。組件不是 MVVM 或 MV* 的更好實現嗎?
如前所述,對於當代架構而言這只是一個底層的問題。然而,當代模式是關於整個應用的推斷。它們處理的是更高級別的抽象。UI 組件是一個原子單元,它從父級接收全局狀態切片,將其與本身的本地狀態組合,並將輸出顯示給用戶。
單向模式能夠解決更大的難題。它說的是在兄弟組件之間通訊並維護應用程序範圍的狀態。若是組件容許垂直分工,這些模式會爲整個應用程序帶回水平分工。
Web 組件的場景若是仍是有些糊塗,請考慮 Vue.js 這個例子。Vue 組件是 MVVM 的完美實現,同時咱們能夠使用 Vuex(Vue 的單向狀態容器)來管理應用程序範圍的狀態。架構存在於多個抽象層次。
組件架構幾乎是全部框架的基礎,人們正在嘗試將組件的概念標準化爲官方 Web 標準,儘管它們背後的推斷方式徹底不一樣。此外我將它們稱爲嘗試,由於即便成爲了標準,許多框架做者也擔心其可行性。
在本文中,最重要的關注點是數據流。Tom Dale 很好地總結了這個問題:
根據個人經驗,與框架無關的組件還有很長的路要走。
關於其餘問題須要看 Rich Harris 的博文《爲何我不用 Web 組件》:
https://dev.to/richharris/why-i-don-t-use-web-components-2cia?source=post_page-----fb5b500b0231----------------------
這並非說當咱們定義本身的技術棧時應該徹底避免它們。通常的建議是從按鈕、複選框和收音機等枝葉組件開始一步步慢慢來,老是要謹慎行事。
函數組件和 Hooks——這是啥?當咱們將組件看成 MVVM 的實現時,咱們一般指望一個視圖模型對象,它的 props 和方法由視圖經過綁定使用。在 React、Vue 和 Angular 等狀況下,它一般是類實例。可是這些框架還有函數組件(沒有任何本地狀態的組件)的概念,其中實例根本不存在。此外,React 最近引入了一種使用 Hooks 編寫組件的新方法,容許咱們在沒有類語法的狀況下編寫有狀態組件。
React Hooks——你注意到這裏缺乏「this」指針了嗎?
這裏的問題是——視圖模型對象在哪裏?Hooks 的創意很簡單,但在跨調用維護本地狀態的理念上徹底不同。但從架構的角度來看它仍然是以前的理念。咱們能夠將其視爲簡單的語法級別更改。咱們都知道 JavaScript 語言中的類很糟糕,常常使人困惑,讓開發人員很難編寫乾淨的代碼。Hooks 擺脫了類,從而解決了這個問題。
惟一改變的是視圖模型的概念。不管是帶有 Hooks 仍是函數組件的有狀態組件,咱們均可以假設組件的視圖模型對象是它的詞法語境(Lexical Context) 或閉包。該組件接收的全部變量、Hooks 值或 props 共同造成其視圖模型。其餘框架也採用了這一理念。
當代架構的下一階段看起來函數組件就是將來趨勢。不過我不會說 Hooks 是一個功能齊全的長期解決方案(聽起來好奇怪),但在語法層面上它很優雅,而且能夠緩解古老的類問題。若是你不認爲語法很重要,請看Svelte:https://svelte.dev/。
與 Web 應用程序相關的每項新技術都會在某種程度上影響前端應用程序。目前有三種趨勢——GraphQL、*** 和編譯器,這裏必須具體介紹一下才算完整。
GraphQLGraphQL 是一種服務端查詢語言。你可能已經看過有人說它取代了 REST,但事實並不是如此。當咱們談論 REST 時,它是一種元模式。在概念層面,它採用面向資源的架構來定義應用程序域模型。在實現層面,它使用 HTTP 協議的語義來交換這些資源,以賦予 Web 共享信息的方式。
現代業務需求很複雜,許多工做流程不能簡單地做爲 HTTP CRUD 類比的資源公開。這就是 REST 比較尷尬的地方。GraphQL 旨在消息傳遞級別替換 REST 的純 HTTP 協議。GraphQL 提供了本身的消息傳遞封裝,能夠被 GraphQL 服務器理解,而且還支持查詢服務端資源(域模型)。
可是 GraphQL 客戶端實現的反作用是,GraphQL 已經開始侵佔狀態容器的職責。咱們來看基本的事實,那就是客戶端的模型只是服務端模型的一個子集,前者專門針對 UI 操做標準化,那麼像 Redux/Flux 這樣的狀態容器只是在客戶端緩存數據而已。
GraphQL 客戶端內置了緩存支持,可跨多個請求來緩存。
這個簡單的事實讓開發人員能夠省掉許多與狀態管理相關的樣板代碼。在宏觀層面,它的形態仍然有待觀察。如下帖子詳細描述了具體的機制:
GraphQL是怎樣取代Redux的。
https://hackernoon.com/how-graphql-replaces-redux-3fff8289221d?source=post_page-----fb5b500b0231----------------------
用React Apollo瘦身咱們的Redux代碼。
https://blog.apollographql.com/reducing-our-redux-code-with-react-apollo-5091b9de9c2a?source=post_page-----fb5b500b0231----------------------
必定要探索 GraphQL,由於它是將來趨勢。
***——服務端渲染過去服務端 MVC 是很是重要的,服務器會生成靜態或動態 HTML 頁面,這些頁面很容易被搜索引擎抓取。因爲客戶端框架能夠提供出色的用戶體驗,咱們正逐漸在瀏覽器上渲染全部內容。徹底客戶端渲染的應用程序在請求服務器時返回的典型 HTML 頁面幾乎是一個空頁面:
SPA 應用程序的初始 HTML 文件——幾乎是空的!
通常來講這沒什麼問題,但在構建電子商務網站時遇到了麻煩,由於這類網站須要較快的加載速度和對 SEO 友好的可抓取內容。麻煩還不止於此,移動電話的互聯網鏈接速度也很慢,而一些入門級設備的硬件也不好。搜索引擎對徹底客戶端渲染的應用程序抓取的能力有限。
爲了緩解這個問題,老式的 ***——服務端渲染 又回來了。有了 ***,咱們能夠在服務器上渲染客戶端 SPA,合併狀態,而後將完整渲染的頁面發送到客戶端。它減小了應用程序頁面的初始加載時間,從而提升了網站的響應速度。
*** 是下一步進化,它填補了客戶端和服務端之間的鴻溝。
因爲客戶端代碼是 JavaScript,咱們須要服務端的等效引擎來執行 JS 代碼。Node.js 做爲 JavaScript 引擎是執行 *** 的服務端技術棧的事實標準。雖然 *** 設置可能變得很是醜陋和複雜,但許多流行的框架已經提供了一流的工具和更高級別的框架,爲 *** 提供了很是流暢的開發人員體驗。
*** 完全改變了咱們的平常開發工做流程。咱們比客戶端——服務端抽象更進一步。它引入了強制的三層架構,其中基於 Node.js 的服務器是關鍵的中間件。但從架構的角度來看——在數據流和分工層面全部這些都是相同的。*** 既不引入新的數據流,也沒有改變已有的存在。
編譯器時代:Svelte——編譯器仍是縮小的框架?我不知道該如何描述 Svelte纔好。我能說的是——當用戶啓動 Web 應用程序時,框架就在瀏覽器中運行。框架提供的轉換抽象有其運行時成本。Svelte 是不同的。
因此 Svelte 是一個基於組件的框架,它展現了當代前端框架的全部特性,但同時它也是一個 編譯器。編譯器將源代碼編譯爲高性能的命令式 JavaScript 代碼。做爲一個編譯器,它能夠作許多其餘框架不能作的事情:與靜態站點構建器同樣,Svelte 在構建時運行,將組件轉換爲高效的命令式代碼,從外部更新 DOM。
在不破壞語法的前提下將 DSL 整合到 JavaScript 中。
其宗旨是編寫更少的代碼。Svelte 證實編譯器能夠實現許多之前用純 JavaScript 沒法實現的功能。若是 Svelte 還不夠,那麼咱們能夠用 Stencil.js,這是一個用於編寫 Web 組件的 TypeScript+JSX 編譯器。
其中,一些想法已經成爲某種形式的主流思想——Angular AOT 編譯器和 Vue 單文件組件等等。而後還有其餘人將這種思想推向極致:
http://imba.io/?source=post_page-----fb5b500b0231----------------------
Rich Harris 的這篇演講很好地展現了 Svelte 的底層哲學,並與 React 作了主觀對比:
https://docs.google.com/presentation/d/1PUvpXMBEDS45rd0wHu6tF3j_8wmGC6cOLtOw2hzU-mw/mobilepresent?slide=id.p
還有其餘方法!偉大的單體!一樣,編譯器的前端開發前景也很光明。
雖然完整的客戶端框架如今風靡一時,但它並非惟一的行事方式。Web 依舊是多樣化的。仍然有許多應用程序是服務端驅動的,並且將繼續這樣作。
但這是否意味着它們的體驗會不好?固然不是!架構的設計目標是支持產品,Basecamp 團隊開發的框架 Stimulus就作得很好。要了解他們的理念能夠查閱:
https://m.signalvnoise.com/the-majestic-monolith/?source=post_page-----fb5b500b0231----------------------
它是一個適度的框架,經過輕量級 JavaScript 提高後端渲染頁面的交互性,同時採用最新實踐和最新標準。Stimulus 一般與 Turbolinks並用,以建立一流的 SPA 用戶體驗。(我是 Basecamp 的老用戶了,發現它比其餘許多 SPA 應用程序都更精緻。)
Stimulus 在某種意義上是不同的,由於它是經過 HTML 而非 JavaScript 驅動。狀態在 HTML 而非 JavaScript 對象中維護。數據流很是簡單:控制器附加到 DOM,它公開了能夠附加到操做上的方法,從而執行進一步的操做。
你可能會想到,它很像 Backbone 和 Knockout 的時代——確實如此。目標很簡單——爲後端驅動 Web 提供的交互式前端框架。惟一的不一樣是 Stimulus 採用了現代社區標準和實踐。
Strudel.js是另外一個相似理念的適度框架。在 2019 年,咱們能夠使用像 RE:DOM 這樣的當代 DOM 庫。
雖然它們可能沒法解決當代前端框架面臨的全部問題,但它們給 JavaScript 審美疲勞的世界帶來了一絲喘息之機。
總結只有一個詞能用來描述 GUI 架構——華麗。雖然對於前端軟件開發來講,MVC 做爲一種模式已經逝去了,但原則是永恆不變的。
咱們從原始的 MVC 開始探索了著名的桌面模式。而後咱們轉到 Web 應用程序並使用相同的原則來獲得了流行的模式。以後咱們轉向早期的獨立客戶端模式,最後全面討論了 SPA 框架。
重要的一點是,今天的前端框架是面向組件的,它們將 MVC/MVVM 關注點做爲一個層面,同時要處理新的環境和挑戰。
最後,咱們瀏覽了一遍前端架構的新面孔,包括 JavaScript 驅動的 *** 和 GraphQL 的崛起。同時咱們跳過了許多使人興奮的新技術,如 HTTP2 和 WebAssembly 等,它們能夠改變前端架構的將來,改變咱們對應用程序的見解。
因爲全部這些模式涉及的術語都有所重疊,而且一般是由社區各自獨立開發的,所以很難根據進化時間來定義明確的線性時間軸。有時將不一樣的模式聯繫在一塊兒是有問題的。此外爲了簡單起見,咱們能夠自由地描述某些概念,不用特別研究它們的細節。沒有現成的命名法則可用,因此有些理念我用了本身發明的術語。
Web 應用程序拓撲是 Web 應用程序架構的另外一個關係密切的領域。拓撲一般在技術棧選擇、安全約束和性能等方面對前端開發產生深遠影響。所以這將是下一篇文章的主題。
咱們但願本文可以幫助你更好地理解前端架構的元素。求分享擴散!
英文原文: https://blog.webf.zone/contemporary-front-end-architectures-fb5b500b0231
活動推薦IJKPlayer 是 BiliBIli 開源的一款基於 ffmpeg 的優秀的播放器,它支持 Android 和 iOS 雙平臺, API 易於集成,編譯配置可裁剪,方便控制安裝包大小,還支持硬件加速解碼更省電......
最重要的一點是 IJKPlayer 還能夠實現跨平臺。此次 GMTC 全球大前端技術大會,咱們專門請到了 B 站移動技術部負責移動端 IJKPlayer 相關工做的資深開發工程師鄭翰超,作此次技術分享,從 B 站內部視角看看 IJKPlayer 的前世此生,全面瞭解這款播放器的演進之路。掃描下方二維碼或點擊閱讀原文,查看詳情。
目前 GMTC 深圳站 8 折售票通道已經開啓,詳細請諮詢:13269078023(同微信)。