問題一:ReactJS組件難以在複雜交互頁面中複用
ReactJS中的最小複用單位是組件。ReactJS的組件比AngularJS的Controller和View 要輕量些。 每個組件只需要前端開發者提供一個 render
函數,把 props
和 state
映射成網頁元素。
這樣的輕量級組件在渲染簡單靜態頁面時很好用, 但是如果頁面有交互,就必須在組件間傳遞迴調函數來處理事件。
我將在《More than React(二)組件對複用性有害?》中用原生DHTML API、ReactJS和Binding.scala實現同一個需要複用的頁面,介紹Binding.scala如何簡單實現、簡單複用複雜的交互邏輯。
問題二:ReactJS的虛擬DOM 算法又慢又不準
ReactJS的頁面渲染算法是虛擬DOM差量算法。
開發者需要提供 render
函數,根據 props
和 state
生成虛擬 DOM。 然後 ReactJS 框架根據 render
返回的虛擬 DOM 創建相同結構的真實 DOM.
每當 state
更改時,ReacJS 框架重新調用 render
函數,獲取新的虛擬 DOM 。 然後,框架會比較上次生成的虛擬 DOM 和新的虛擬 DOM 有哪些差異,然後把差異應用到真實DOM上。
這樣做有兩大缺點:
- 每次
state
更改,render
函數都要生成完整的虛擬 DOM. 哪怕state
改動很小,render
函數也會完整計算一遍。如果render
函數很複雜,這個過程就白白浪費了很多計算資源。 - ReactJS框架比較虛擬DOM差異的過程,既慢又容易出錯。比如,假如你想要在某個
<ul>
列表的頂部插入一項<li>
,那麼ReactJS框架會誤以爲你修改了<ul>
的每一項<li>
,然後在尾部插入了一個<li>
。
這是因爲 ReactJS收到的新舊兩個虛擬DOM之間相互獨立,ReactJS並不知道數據源發生了什麼操作,只能根據新舊兩個虛擬DOM來猜測需要執行的操作。 自動的猜測算法既不準又慢,必須要前端開發者手動提供 key
屬性、shouldComponentUpdate
方法、componentDidUpdate
方法或者 componentWillUpdate
等方法才能幫助 ReactJS 框架猜對。
我將在《More than React(三)虛擬DOM已死?》中比較ReactJS、AngularJS和Binding.scala渲染機制,介紹簡單性能高的Binding.scala精確數據綁定機制。
問題三:ReactJS的HTML模板功能既不完備、也不健壯
ReactJS支持用JSX編寫HTML模板。
理論上,前端工程師只要把靜態HTML原型複製到JSX源文件中, 增加一些變量替換代碼, 就能改造成動態頁面。 理論上這種做法要比Cycle.js、Widok、ScalaTags等框架更適合複用設計師提供的HTML原型。
不幸的是,ReactJS對HTML的支持殘缺不全。開發者必須手動把class
和for
屬性替換成className
和htmlFor
,還要把內聯的style
樣式從CSS語法改成JSON語法,代碼才能運行。 這種開發方式下,前端工程師雖然可以把HTML原型複製粘貼到代碼中,但還需要大量改造才能實際運行。 比Cycle.js、Widok、或者、ScalaTags省不了太多事。
除此之外,ReactJS還提供了propTypes
機制校驗虛擬DOM的合法性。 然而,這一機制也漏洞百出。 即使指定了propTypes
,ReactJS也不能在編譯前提前發現錯誤。只有測試覆蓋率很高的項目時才能在每個組件使用其他組件時進行校驗。 即使測試覆蓋率很高,propTypes
仍舊不能檢測出拼錯的屬性名,如果你把onClick
寫成了onclick
, ReactJS就不會報錯,往往導致開發者額外花費大量時間排查一個很簡單的bug。
我將在《More than React(四)HTML也可以編譯?》中比較ReactJS和Binding.scala的HTML模板,介紹Binding.scala如何在完整支持XHTML語法的同時靜態檢查語法錯誤和語義錯誤。
問題四:ReactJS與服務器通信時需要複雜的異步編程
ReactJS從服務器加載數據時的架構可以看成MVVM(Model–View–ViewModel)模式。 前端工程師需要編寫一個數據庫訪問層作爲Model,把ReactJS的state
當做ViewModel,而render
當做View。 Model負責訪問數據庫並把數據設置到state
(即View Model)上,可以用Promise和fetch API實現。 然後,render
,即View,負責把View Model渲染到頁面上。
在這整套流程中,前端程序員需要編寫大量閉包組成的異步流程, 設置、訪問狀態的代碼五零四散, 一不小心就會bug叢生,就算小心翼翼的處理各種異步事件,也會導致程序變得複雜,既難調試,又難維護。
我將在《More than React(五)爲什麼別用異步編程?》中比較ReactJS和Binding.scala的數據同步模型,介紹Binding.scala如何自動同步服務器數據,避免手動異步編程。
結論
儘管Binding.scala初看上去很像ReactJS, 但隱藏在Binding.scala背後的機制更簡單、更通用,與ReactJS和Widok截然不同。
所以,通過簡化概念,Binding.scala靈活性更強,能用通用的方式解決ReactJS解決不了的複雜問題。
比如,除了上述四個方面以外,ReactJS的狀態管理也是老大難問題,如果引入Redux或者react-router這樣的第三方庫來處理狀態,會導致架構變複雜,分層變多,代碼繞來繞去。而Binding.scala可以用和頁面渲染一樣的數據綁定機制描述複雜的狀態,不需要任何第三方庫,就能提供服務器通信、狀態管理和網址分發的功能。
以下表格中列出了上述Binding.scala和ReactJS的功能差異:
Binding.scala | ReactJS | ||
---|---|---|---|
Binding.scala | ReactJS | ||
複用性 | 最小複用單位 | 方法 | 組件 |
複用難度 | 不論交互內容還是靜態內容都容易複用 | 容易複用靜態內容組件,但難以複用交互組件 | |
頁面渲染算法 | 算法 | 精確的數據綁定 | 虛擬 DOM |
性能 | 高 | 低 | |
正確性 | 自動保證正確性 | 需要開發者手動設置 key 屬性,不然複雜的頁面會錯亂。 | |
HTML 模板 | 語法 | Scala XML 字面量 | JSX |
是否支持 HTML 或 XHTML 語法 | 完整支持 XHTML | 殘缺支持。正常的 XHTML 無法編譯。開發者必須手動把 class 和 for 屬性替換成 className 和 htmlFor ,還要把內聯的 style 樣式從 CSS 語法改成 JSON 語法。 | |
如何校驗模板語法 | 自動編譯時校驗 | 運行時通過 propTypes 校驗但無法檢測簡單的拼寫錯誤。 | |
服務器通訊 | 機制 | 自動遠程數據綁定 | MVVM + 異步編程 |
實現難度 | 簡單 | 複雜 | |
其他 | 如何分派網址或者錨點鏈接 | 支持把網址當成普通的綁定變量來用,無需第三方庫。 | 不支持,需要第三方庫 react-router |
功能完備性 | 完整的前端開發解決方案 | 本身只包含視圖部分功能。需要額外掌握 react-router 、 Redux 等第三方庫才能實現完整的前端項目。 | |
學習曲線 | API 簡單,對沒用過 Scala 的人來說也很好懂 | 上手快。但功能太弱導致後期學習第三方庫時曲線陡峭。 |
兩個多月前,我在Scala.js的論壇發佈Binding.scala時,當時Scala.js社區最流行的響應式前端編程框架是Widok。Tim Nieradzik是Widok的作者。他在看到我發佈的框架後,稱讚這個框架是Scala.js社區最有前途的 HTML 5渲染框架。
他是對的,兩個月後,現在Binding.scala已經成爲Scala.js社區最流行的響應式前端編程框架。
Awesome Scala網站對比了Scala的響應式前端編程框架,Binding.scala的活躍程度和流行度都比Udash、Widok等其他框架要高。
我在最近的幾個項目中,也逐漸放棄JavaScript和ReactJS,改用Scala.js和Binding.scala搭建新時代的前端技術棧