SparkUI:一個可供參考的前端開發實踐

寫在前面

SparkUI 是一套完整且靈活的前端開發解決方案。該方案基於 React,由 Modula 應用狀態管理框架、一系列可重用的前端組件、以及構建 SPA 所需的各類支持庫組成。該方案重視可重用性、靈活性、可測試性以及開發效率,解決了前端社區常見的一些針對商業前端應用開發的痛點,如複雜狀態、Side Effect,組件拆分等,更在工程實踐、文檔化、本身代碼質量等方面達到較高標準,為前後端分離架構下的商業前端應用開發提供了堅實的基礎。目前 SparkUI 已成功應用在 FreeWheel 的前端項目中。

Spark UI 的誕生和演進

技術選型

FreeWheel 產生升級原有前端框架、開始重新設計 SparkUI 的想法大約始於兩年前。2015 年 7 月之前,FreeWheel 其實已有一套基於 jQuery 的前端開發框架,但由於當時 jQuery 的版本較老、技術棧陳舊、整個框架維護不佳,而且缺乏一套可供新同事學習的文檔,再加之 React 的興起,因此我們決定對原有前端框架進行升級。

另一方面,縱觀整個大局勢下,也有越來越多的互聯網公司都在轉向 React.js 去開發前端組件,除了性能因素外,很大一部分原因是用 jQuery 去寫很複雜的 DOM 操作,後期代碼會變得越來越難維護。

當時,JavaScript 框架中的“當紅者”除了 React 外還包括 Angular、Vue 等,我們的前端團隊主要對 React 和 Angular 兩者進行了技術選型的評估。評估的各項指標顯示,React.js 相對 Angular.js 的學習曲線更低,而且也較為符合我們當時的基本想法——使用組件化的設計思想,所以,基於 React 開發前端開發框架的行動也隨之展開。

SparkUI:一個可供參考的前端開發實踐

FreeWheel 新老前端框架對比

迭代升級

FreeWheel 真正開始啟動 SparkUI 是在一年半之前,這一年多也經歷了幾次比較大的迭代,從最初的 0.X 版本到 Spark 1.0 的版本,大概是以下幾個逐步過渡階段:

0.X 版本主要是基於 Flux 的應用狀態管理框架。因為 React 本身只是在 MVC 架構下的視圖這一層,只能用來構建視圖組件(View Component),本身並沒有狀態管理的能力,而頂層組件的狀態也愈發難以維護,因此當時的 React 生態圈中,React+Flux 這類比較經典的架構在 FreeWheel 之外的其他許多公司也得到更為頻繁的應用。

但伴隨 FreeWheel 前端應用過程中頁面交互越來越複雜,組件繁多的同時,各個組件狀態又非常繁雜,而且狀態間存在諸多映射關係,因此 Flux 在狀態管理中的表現也越來越滿足不了我們的需求。當時開發社區中出現了新的狀態管理的理念和技術——Redux,所以在從 0.X 版本提升到 Spark1.0 的過程中,我們在狀態管理設計的思想上也從 Flux 切換到了 Redux。

1.0 版本之後已經全面引入了 Redux 作為狀態管理的框架,但這並不意味著是完全照搬 Redux 的狀態管理機制。實際上,FreeWheel 僅僅用到了 Redux 基本狀態的存取接口和一些基本工具,在此基礎上, SparkUI 框架的核心模塊(其內部稱之為 Modula)在整個狀態管理中扮演了重要的角色,這其中也引入了諸如對象樹(Model Tree)這類的概念——因為在 Redux 中的狀態並沒有層級,而都是平行展開,但 FreeWheel 的應用中,一個對象可能存在非常複雜的層級結構,所以需要引入 Model Tree 對應用數據進行集中管理(Modula 相關設計理念將在後文中具體闡述)。

SparkUI:一個可供參考的前端開發實踐

各階段中的應用狀態管理框架對比

除了狀態管理框架的調整之外,1.0 還引入了“函數式編程”的理念。所謂的“函數式編程”理念,是將在框架內部狀態的流轉完全用一種函數式的編程方式來實現。“函數式編程”最大的優勢,就是把一個程序動態運行的過程以一種函數的方式來將其抽象。如果狀態扭轉過程是一個函數的話,其實能夠保證傳給函數的原始對象的狀態不被改變,只是函數以新的狀態輸出。當一個應用比較複雜的時候,這樣能夠保證高效地跟蹤和管理應用狀態的改變。在 SparkUI 框架裡,它也正作為一個基本的編程範式在使用。

同時,社區對“函數式編程”中 Side Effect 的作用一直有不同的聲音,但在實際應用中,我們發現 Side Effect 很難避免。比如說兩個組件之間在操作上存在關聯性——在一個 Grid 裡操作完之後勢必要影響另外一個 Grid 的展示或行為,這個過程我們也是通過 Side Effect 來支持的。

因此可以說,Spark1.0 基本完成了對 0.X 版本的一次徹底的革新,但同時也導致原有產品中使用 0.X 的頁面和應用需要切換到完全不同的 API,這個過程也成為我們的一段重要經驗。之後框架的升級改造,我們堅持的基本原則是,所有的改變都是以向後兼容(Backward Compatible)的方式修改,給應用方提供平滑過渡的過程。到目前為止,1.0 版本的整個框架處於相對比較穩定的狀態,也已經在我們的生產環境裡廣泛使用。

SparkUI 架構整體解析

SparkUI,可以理解為我們所謂的分層設計理念,整個 SparkUI 的架構和功能如下:

  • 最底層就是 React 提供的 API,主要提供了基本組件的創建,包括生命週期管理的 API 等等。

  • 接著在此之上封裝了 Modula 模塊,Modula 模塊主要是做應用狀態的管理,其本身還是使用了 Redux 來進行實際狀態的存取和事件的分發, 並利用 Immutable.js 來保證對象樹(Model Tree)的不可變性。

  • Modula 之上提供了一部分狀態管理中所用到的、做 SPA(Single Page Application)所依賴的工具。

  • 這層工具之上是 SparkUI 的組件庫。因為 FreeWheel 當前主要以商業應用為主,其特點在於界面的變化和演進相對而言會更慢,但卻特別強調新、舊界面間的高度一致性。所以我們在應用中抽象出了前端的組件庫(比如有稱之為 Grid——高度定製化的一種表單的組件等),而所有的應用又會利用這些組件實現它們各自的功能。

SparkUI:一個可供參考的前端開發實踐

SparkUI 框架

Modula 模塊

SparkUI 框架的設計過程中其實吸收了很多 Redux 狀態管理的思想,現在也是使用了 Redux 來進行應用狀態的存取和事件的分發,但和 Redux 最大的區別在於,狀態管理複雜程度以及應用狀態數量不同,其管理思路也具有一定的差異性。

上文中提到的 SparkUI 框架核心模塊——Modula 實際上就是基於 Redux(但並不限於 Redux)的管理,它與部分 Redux 生態(如 Redux-devtools)兼容,且已完整封裝並隱藏了底層的 Redux。下圖簡要介紹了 Modula 與 React、Redux 的關係:

SparkUI:一個可供參考的前端開發實踐SparkUI:一個可供參考的前端開發實踐

Modula 應用狀態管理框架

例如,在 Redux 裡,應用狀態的結構完全是平展開的,不存在任何的層級關係,因為缺乏一個對象化的組織,所以要在狀態眾多的情況下,在 Redux 的 Store 上找到某個狀態就只能依靠純記憶。而 Modula 引入了對象樹(Model Tree)後,所有的狀態都可以被對象化,即通過預先定義好的結構來組織狀態。儘管是比較複雜的組件,在頁面上的展示可能也只是一個表單或 Table。

如果給這個 Table 設定一個較為複雜的狀態——加一個搜索條,搜索條本身有簡單搜索和複雜搜索的區分,上面還有複雜的工具欄、動作條,其本身或許還需要支持翻頁等。如此多的狀態之下,用 Redux 的方式可能會有好幾百個狀態在一個 Store 裡,於是管理起來就會非常困難;但 Modula 就可以組織得更好,下面是 Modula 主要的設計理念:

  • Application State=Initial State+Deltas,其中 Delta 是由 Actions 觸發的(借鑑 Flux, Elm);

  • Application State 可以由一棵 Model Tree 來描述,這棵樹的每個節點都是一個可以描述有效業務實體的 Model(借鑑 Redux,Elm);

  • 由一個給定的 Application State 到另一個 State 的 Transition 可以由 Model Tree 提供的 Reactions 所描述,一次成功的 Action 到 Reaction 的匹配會將 Model Tree 演變為下一個狀態(原創);

  • Side Effect 是上述 State Transitions 的結果,它包含了一個更新的 Model 實例,以及零至多個 Callback Functions(借鑑 Elm);

對於 Modula 中 Side Effect 問題的處理,Modula 模塊中的 Receiver 可以返回 Side Effect,一個 Side Effect 可以是 Sender 或 Bubble Event 的引用,也可以是一段匿名函數(箭頭函數);List-A 讀取完成時會根據 List-A 中包含的 ID,自動觸發讀取 List-B。

SparkUI:一個可供參考的前端開發實踐

所以目前在狀況管理上,Modula 相對於 Redux 會是比較適應複雜前端狀態的應用改進。

前端路由框架 Spark Router

此外,為了能夠支持構建典型的 SPA,我們開發了一個叫 Spark Router 的組件。它主要也是基於 Modula,相比於 React Router(其狀態並不存儲在 Redux 的 Store 上),Spark Router 裡的狀態管理能夠和應用中其他部分的狀態管理採用同樣的機制。

此前,應用狀態都分散在 React Router 的 State 與 Modula Model(Redux state)裡,兩者經常遇到同步問題,我們的解決方案是將路由相關的 State 也合併進 Modula 中。因而,Spark Router 主要是針對 Model 配置路由,Component 可根據 Model 切換相應界面,這樣就不必再在 Spark Router 的狀態管理和應用中其他部分的狀態管理之間添加同步設計,也讓程序變得更簡單。

SparkUI 當前的應用

SparkUI 目前已經在 FreeWheel 的生產環境中使用了超過一年的時間。我們內部幾乎所有的 UI 產品都開始在使用 SparkUI。但現階段還仍處於從舊有實踐過渡到新的基於 SparkUI 實踐的過程。

我們之所以會自己設計和搭建基於 React 的前端 MVC 解決方案,也在於 FreeWheel 的系統是廣告資源管理系統,該系統的客戶群體大多有非常複雜的工作流,並且是要通過 UI 來實現,因而導致了前端的應用狀態多且複雜。所以,SparkUI 框架的特點,或者說其應用場景即:擅長於用來構建有比較複雜的前端狀態的應用。

我們在實踐過程中也借鑑了很多業界比較好的實踐,包括 Redux、Mobx 等,而且能被重用的東西我們都儘量重用,比如 Elemental 這類基礎組件,這樣可以在很大程度上降低使用者在 SparkUI 上需要的額外學習成本。此外,我們專門為 SparkUI 做了一個 Documentation Portal,其中有非常豐富的演示能幫助需要使用 SparkUI 的同事來學習。目前,在 FreeWheel 內部,前端團隊會定期給其他團隊做使用交流和分享,並及時同步最新的一些改進。

SparkUI 目前還只是供 FreeWheel 公司內部使用,暫未開源。但據瞭解,FreeWheel 和其母公司 Comcast 都非常鼓勵開源,目前也已開始對開源 SparkUI 走相關法務流程。FreeWheel 首席架構師張晗表示:“SparkUI 可能並不會全部開源,如組件庫這類屬於產品特定需求的部分會被拿掉,像 Modula 這樣的通用模型部分則會屬於開源的範疇。如果你的應用需要複雜的前端功能,特別是需要對具有相互關係的狀態進行較多維護時,就可以考慮使用我們的 Modula。”

SparkUI 的未來規劃

首先,我們目前需要對框架核心的升級進行性能優化。雖然 React 框架本身的性能也在不斷優化,但它其實並不以性能見長,影響性能很重要的一點就是狀態變化的計算,根據狀態變化的計算來重新渲染頁面。尤其當狀態比較多的時候,此類檢查就會比較費時。對此,我們會提出並引入一些標記方式,通過在對象樹(Model Tree)上標識一個範圍,只要在這個範圍裡的子狀態被更新,服務狀態所對應到的視圖(View)就需要重新被渲染(Render)。

其次,我們還在不斷完善單頁 Web 應用的資質。因為 FreeWheel 在做新的前端框架以及前端產品的升級過程中也同時在做前後端的分離。同時,我們的業務系統也做前後端的分離。所以說我們也希望把所有的展示邏輯、非數據邏輯都儘量地移到前端實現,最終整個應用可以變成 SPA。目前,我們已經有對路由、國際化的支持,現階段還在開發權限模塊,目前的權限控制是在服務器端,接下來的目標是增強對權限控制、會話(Session)的管理。

今日薦文

點擊下方圖片即可閱讀

SparkUI:一個可供參考的前端開發實踐

FreeWheel 容力:如何打造更高質效的技術團隊

相關推薦

推薦中...