《More than React》系列的文章會(huì)一共分為五篇。本文是第一篇刹缝,介紹用ReactJS開發(fā)時(shí)遇到的種種問題碗暗。后面四篇文章的每一篇將會(huì)分別詳細(xì)討論其中一個(gè)問題颈将,以及Binding.scala如何解決這個(gè)問題。
背景介紹
去年 4 月讹堤,我第一次在某個(gè)客戶的項(xiàng)目中接觸到ReactJS 吆鹤。
我發(fā)現(xiàn)ReactJS要比我以前用過的AngularJS簡單很多,它提供了響應(yīng)式的數(shù)據(jù)綁定功能洲守,把數(shù)據(jù)映射到網(wǎng)頁上疑务,使我可以輕松實(shí)現(xiàn)交互簡單的網(wǎng)站。
然而梗醇,隨著我越來越深入的使用ReactJS知允,我發(fā)現(xiàn)用ReactJS編寫交互復(fù)雜的網(wǎng)頁很困難。
我希望有一種方式叙谨,能夠像ReactJS一樣簡單解決簡單問題温鸽。此外,還要能簡單解決復(fù)雜問題手负。
于是我把ReactJS用Scala重新寫了一個(gè)涤垫。代碼量從近三萬行降到了一千多行。
用這個(gè)框架實(shí)現(xiàn)的TodoMVC應(yīng)用竟终,只用了154行代碼蝠猬。而用ReactJS實(shí)現(xiàn)相同功能的TodoMVC,需要488行代碼统捶。
下圖是用Binding.scala實(shí)現(xiàn)的TodoMVC應(yīng)用榆芦。
這個(gè)框架就是Binding.scala。
問題一:ReactJS組件難以在復(fù)雜交互頁面中復(fù)用
ReactJS中的最小復(fù)用單位是組件喘鸟。ReactJS的組件比AngularJS的Controller和View 要輕量些匆绣。
每個(gè)組件只需要前端開發(fā)者提供一個(gè) render
函數(shù),把 props
和 state
映射成網(wǎng)頁元素什黑。
這樣的輕量級(jí)組件在渲染簡單靜態(tài)頁面時(shí)很好用崎淳,
但是如果頁面有交互,就必須在組件間傳遞回調(diào)函數(shù)來處理事件愕把。
我將在《More than React(二)組件對復(fù)用性有害凯力?》中用原生DHTML API、ReactJS和Binding.scala實(shí)現(xiàn)同一個(gè)需要復(fù)用的頁面礼华,介紹Binding.scala如何簡單實(shí)現(xiàn)咐鹤、簡單復(fù)用復(fù)雜的交互邏輯。
問題二:ReactJS的虛擬DOM 算法又慢又不準(zhǔn)
ReactJS的頁面渲染算法是虛擬DOM差量算法圣絮。
開發(fā)者需要提供 render
函數(shù)祈惶,根據(jù) props
和 state
生成虛擬 DOM。
然后 ReactJS 框架根據(jù) render
返回的虛擬 DOM 創(chuàng)建相同結(jié)構(gòu)的真實(shí) DOM.
每當(dāng) state
更改時(shí),ReacJS 框架重新調(diào)用 render
函數(shù)捧请,獲取新的虛擬 DOM 凡涩。
然后,框架會(huì)比較上次生成的虛擬 DOM 和新的虛擬 DOM 有哪些差異疹蛉,然后把差異應(yīng)用到真實(shí)DOM上活箕。
這樣做有兩大缺點(diǎn):
- 每次
state
更改,render
函數(shù)都要生成完整的虛擬 DOM. 哪怕state
改動(dòng)很小可款,render
函數(shù)也會(huì)完整計(jì)算一遍育韩。如果render
函數(shù)很復(fù)雜,這個(gè)過程就白白浪費(fèi)了很多計(jì)算資源闺鲸。 - ReactJS框架比較虛擬DOM差異的過程筋讨,既慢又容易出錯(cuò)。比如摸恍,假如你想要在某個(gè)
<ul>
列表的頂部插入一項(xiàng)<li>
悉罕,那么ReactJS框架會(huì)誤以為你修改了<ul>
的每一項(xiàng)<li>
,然后在尾部插入了一個(gè)<li>
立镶。
這是因?yàn)?ReactJS收到的新舊兩個(gè)虛擬DOM之間相互獨(dú)立壁袄,ReactJS并不知道數(shù)據(jù)源發(fā)生了什么操作,只能根據(jù)新舊兩個(gè)虛擬DOM來猜測需要執(zhí)行的操作媚媒。
自動(dòng)的猜測算法既不準(zhǔn)又慢然想,必須要前端開發(fā)者手動(dòng)提供 key
屬性、shouldComponentUpdate
方法欣范、componentDidUpdate
方法或者 componentWillUpdate
等方法才能幫助 ReactJS 框架猜對。
我將在《More than React(三)虛擬DOM已死令哟?》中比較ReactJS恼琼、AngularJS和Binding.scala渲染機(jī)制,介紹簡單性能高的Binding.scala精確數(shù)據(jù)綁定機(jī)制屏富。
問題三:ReactJS的HTML模板功能既不完備晴竞、也不健壯
ReactJS支持用JSX編寫HTML模板。
理論上狠半,前端工程師只要把靜態(tài)HTML原型復(fù)制到JSX源文件中噩死,
增加一些變量替換代碼,
就能改造成動(dòng)態(tài)頁面神年。
理論上這種做法要比Cycle.js已维、Widok、ScalaTags等框架更適合復(fù)用設(shè)計(jì)師提供的HTML原型已日。
不幸的是垛耳,ReactJS對HTML的支持殘缺不全。開發(fā)者必須手動(dòng)把class
和for
屬性替換成className
和htmlFor
,還要把內(nèi)聯(lián)的style
樣式從CSS語法改成JSON語法堂鲜,代碼才能運(yùn)行栈雳。
這種開發(fā)方式下,前端工程師雖然可以把HTML原型復(fù)制粘貼到代碼中缔莲,但還需要大量改造才能實(shí)際運(yùn)行哥纫。
比Cycle.js、Widok痴奏、或者蛀骇、ScalaTags省不了太多事。
除此之外抛虫,ReactJS還提供了propTypes
機(jī)制校驗(yàn)虛擬DOM的合法性松靡。
然而,這一機(jī)制也漏洞百出建椰。
即使指定了propTypes
雕欺,ReactJS也不能在編譯前提前發(fā)現(xiàn)錯(cuò)誤。只有測試覆蓋率很高的項(xiàng)目時(shí)才能在每個(gè)組件使用其他組件時(shí)進(jìn)行校驗(yàn)棉姐。
即使測試覆蓋率很高屠列,propTypes
仍舊不能檢測出拼錯(cuò)的屬性名,如果你把onClick
寫成了onclick
伞矩,
ReactJS就不會(huì)報(bào)錯(cuò)笛洛,往往導(dǎo)致開發(fā)者額外花費(fèi)大量時(shí)間排查一個(gè)很簡單的bug。
我將在《More than React(四)HTML也可以編譯乃坤?》中比較ReactJS和Binding.scala的HTML模板苛让,介紹Binding.scala如何在完整支持XHTML語法的同時(shí)靜態(tài)檢查語法錯(cuò)誤和語義錯(cuò)誤。
問題四:ReactJS與服務(wù)器通信時(shí)需要復(fù)雜的異步編程
ReactJS從服務(wù)器加載數(shù)據(jù)時(shí)的架構(gòu)可以看成MVVM(Model–View–ViewModel)模式湿诊。
前端工程師需要編寫一個(gè)數(shù)據(jù)庫訪問層作為Model狱杰,把ReactJS的state
當(dāng)做ViewModel,而render
當(dāng)做View厅须。
Model負(fù)責(zé)訪問數(shù)據(jù)庫并把數(shù)據(jù)設(shè)置到state
(即View Model)上仿畸,可以用Promise和fetch API實(shí)現(xiàn)。
然后朗和,render
错沽,即View,負(fù)責(zé)把View Model渲染到頁面上眶拉。
在這整套流程中千埃,前端程序員需要編寫大量閉包組成的異步流程,
設(shè)置忆植、訪問狀態(tài)的代碼五零四散镰禾,
一不小心就會(huì)bug叢生皿曲,就算小心翼翼的處理各種異步事件,也會(huì)導(dǎo)致程序變得復(fù)雜吴侦,既難調(diào)試屋休,又難維護(hù)。
我將在《More than React(五)為什么別用異步編程备韧?》中比較ReactJS和Binding.scala的數(shù)據(jù)同步模型劫樟,介紹Binding.scala如何自動(dòng)同步服務(wù)器數(shù)據(jù),避免手動(dòng)異步編程织堂。
結(jié)論
盡管Binding.scala初看上去很像ReactJS叠艳,
但隱藏在Binding.scala背后的機(jī)制更簡單允瞧、更通用烹玉,與ReactJS和Widok截然不同。
所以锨络,通過簡化概念潦俺,Binding.scala靈活性更強(qiáng)拒课,能用通用的方式解決ReactJS解決不了的復(fù)雜問題。
比如事示,除了上述四個(gè)方面以外早像,ReactJS的狀態(tài)管理也是老大難問題,如果引入Redux或者react-router這樣的第三方庫來處理狀態(tài)肖爵,會(huì)導(dǎo)致架構(gòu)變復(fù)雜卢鹦,分層變多,代碼繞來繞去劝堪。而Binding.scala可以用和頁面渲染一樣的數(shù)據(jù)綁定機(jī)制描述復(fù)雜的狀態(tài)冀自,不需要任何第三方庫,就能提供服務(wù)器通信秒啦、狀態(tài)管理和網(wǎng)址分發(fā)的功能熬粗。
以下表格中列出了上述Binding.scala和ReactJS的功能差異:
兩個(gè)多月前,我在Scala.js的論壇發(fā)布Binding.scala時(shí)帝蒿,當(dāng)時(shí)Scala.js社區(qū)最流行的響應(yīng)式前端編程框架是Widok。Tim Nieradzik是Widok的作者巷怜。他在看到我發(fā)布的框架后葛超,稱贊這個(gè)框架是Scala.js社區(qū)最有前途的 HTML 5渲染框架。
他是對的延塑,兩個(gè)月后绣张,現(xiàn)在Binding.scala已經(jīng)成為Scala.js社區(qū)最流行的響應(yīng)式前端編程框架。
Awesome Scala網(wǎng)站對比了Scala的響應(yīng)式前端編程框架关带,Binding.scala的活躍程度和流行度都比Udash侥涵、Widok等其他框架要高沼撕。
我在最近的幾個(gè)項(xiàng)目中,也逐漸放棄JavaScript和ReactJS芜飘,改用Scala.js和Binding.scala搭建新時(shí)代的前端技術(shù)棧务豺。