More than React(一)為什麼ReactJS不適合複雜互動的前端專案?
《More than React》系列的文章會一共分為五篇和一則附錄。本文是第一篇,介紹用 ReactJS開發時遇到的種種問題。後面四篇文章的每一篇將會分別詳細討論其中一個問題,以及Binding.scala如何解決這個問題。附錄是一則指南,指引你從頭一步步建立Binding.scala專案。
背景介紹
去年 4 月,我第一次在某個客戶的專案中接觸到ReactJS 。
我發現ReactJS要比我以前用過的AngularJS簡單很多,它提供了響應式的資料繫結功能,把資料對映到網頁上,使我可以輕鬆實現互動簡單的網站。
然而,隨著我越來越深入的使用ReactJS,我發現用ReactJS編寫互動複雜的網頁很困難。我希望有一種方式,能夠像ReactJS一樣簡單解決簡單問題。此外,還要能簡單解決複雜問題。
於是我把ReactJS用Scala重新寫了一個。程式碼量從近三萬行降到了一千多行。
用這個框架實現的 TodoMVC 應用,只用了154行程式碼。而用ReactJS實現相同功能的TodoMVC,需要488行程式碼。
下圖是用Binding.scala實現的TodoMVC應用。
問題一: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負責訪問後端API並把資料設定到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搭建新時代的前端技術棧。
相關連結
鳴謝
感謝張凱峰和鄭培真幫我檢閱了這一系列文章,你看到的文章的結構和內容許多地方都來自他們提出的修改意見。
相關推薦
More than React(一)為什麼ReactJS不適合複雜互動的前端專案?轉載的
《More than React》系列的文章會一共分為五篇和一則附錄。本文是第一篇,介紹用 ReactJS開發時遇到的種種問題。後面四篇文章的每一篇將會分別詳細討論其中一個問題,以及Binding.scala如何解決這個問題。附錄是一則指南,指引你從頭一步步建立Binding
More than React(一)為什麼ReactJS不適合複雜互動的前端專案?
《More than React》系列的文章會一共分為五篇和一則附錄。本文是第一篇,介紹用 ReactJS開發時遇到的種種問題。後面四篇文章的每一篇將會分別詳細討論其中一個問題,以及Binding.scala如何解決這個問題。附錄是一則指南,指引你從頭一步步建立Bindi
反向教學系列之——Django入門(一)【不需知道web框架】
Django 教程 反向教學 一派胡言 用這東西最終是建網站的,或者是更一般意義的服務器。服務器麽,就是如果用別的電腦(“客戶機”)給它發請求,它會返回一些東西——如果給隨便某個機器發信息,它自然未必理你。要想某機器回應你,得滿足這些條件——它不處在關機狀態它能收到你的信息,你也能收到它的信息
vue Esview 視覺化程式設計程式流程(一)解決不能登入問題
esview的git地址: https://github.com/furioussoul/esview 下載後有兩個工程,一個是server,springboot伺服器端;一個是ui端 Ui端用npm install,npm run dev進行編譯,伺服器端用marven install
我的Android NDK之旅(一),不使用ndk-build命令來建立jni
最近閒來無事,想摸索下一下ndk,可是ndk不是塊好啃的骨頭,但作為一名程式設計師,什麼都要了解下,對吧╮( ̄▽ ̄)╭。首先我想吐槽一下,網上有些部落格寫的很亂,一上來就貼一段程式碼,也不告訴是要幹什麼,程式碼一寫完就完事,這讓初學者很難理解jni到底是個什
create-react-app原始碼解析(一),npm run start如何讓專案跑起來
小編花了點時間,大致弄懂npm run start如何執行專案的原理了,現在給大家分享下心得!npm run start是通過node跑js檔案,從而專案得以執行,小編通過npm run eject拿到了所有配置,然後通過解析原始碼,明白瞭如果通過webpack等讓專
React(一)初識
一 初識React 二 前置知識 三 基本方法 (1) ReactDOM.render是React的最基本方法,用於將模板轉為HTML,並插入指定的DOM節點。 ReactDOM.render{ <h1>he
React(一)
一、簡介React 是一個用於構建使用者介面的JAVASCRIPT 庫。React主要用於構建UI,很多人認為 React 是 MVC 中的 V(檢視)。React 起源於 Facebook 的內部專案,用來架設Instagram 的網站,並於 2013 年 5 月開源。Re
實戰build-react(一)
腳手架 如果 class ini -c 搭建項目 cpp new 運行 1.react-cli安裝腳手架 (第一次)如果安裝過不需要再安裝 npm install -g build-react 2.使用腳手架搭建項目 build-react init my-reac
騰訊雲伺服器的配置與部署(一):雲伺服器的連線、專案(php與phpMyAdmin)的部署
【前言】 最近在開發一個小程式,小程式的服務端打算用php來寫。小程式的wx.request必須是https請求,尋找對比了各種雲伺服器,發現騰訊為了推廣小程式,推出了“3元體驗騰訊雲小程式後端解決方案”活動,於是花3元買了騰訊雲伺服器(順帶的還有一臺雲資料庫
Spring Boot / Spring MVC 入門實踐 (一) :環境搭建與第一個專案
宣告 本系列文章系本人原創,歡迎轉載,轉載請註明出處。 本系列文章通過具體的例子,介紹如何通過Spring Boot來幫助簡化Spring MVC開發Web Application的過程,旨在通過具體的實踐,使讀者能夠入門利用Spring Boot開發Web
Spring Boot 入門 (一)——建一個最簡單的springboot專案
建立一個最簡單的springboot專案 廢話不多說直接開整!! 開啟eclipse新建一個maven專案,目錄格式如下: 開啟pom.xml檔案,引入父依賴,web依賴,並設定JDK版本 <project xmlns="http://maven.apache.or
Laravel 4 系列入門教程(一)【最適合中國人的Laravel教程】
每一個教程完成,我將會git commit一次。 大家在任何地方卡住,最快捷的解決方式就是去看我的示例程式碼。 0. 預設條件 本文預設你已經有配置完善的PHP+MySQL執行環境,懂得PHP網站執行的基礎知識。跟隨本教程走完一遍,你將會得到一個基礎的包含
maven入門淺析(一)-----maven安裝、配置、建立專案骨架、編譯、測試、打包、執行
一、下載及安裝 1.1 下載maven 3.2.5 先到官網http://maven.apache.org/download.cgi 下載3.2.5版本(目前最新是3.3.9),下載完成後,解壓到某個目錄(本文中是E:\apachemaven) 2.1 配置環境變數 系統
GitHub(一)之如何將GitHub上的專案用jenkins進行持續的整合構建部署
最近公司新來的架構師把公司的專案用jenkins持續構建部署,第一次接觸這種自動構建工具的我內心十分的動,再也不用我來把專案打包部署了,簡直嗨的不行!於是到網上收集了一些資料,自己琢磨了一陣子,現在把自己琢磨出來的東西分享記錄一下,有錯誤的地方歡迎大家指正。 另外在
【Maven】Maven系列(一)——環境安裝配置和新建maven專案
1 Maven是什麼? Apache Maven 是一種用作軟體專案管理和理解工具。它基於專案物件模型(POM)的概念, 可以管理一個專案的構建、報告以及從專案核心資訊中生成文件。 Maven是一種專案管理和理解工具。Maven向開發者提供了一個完整的構建生
Redis原始碼剖析(一)伺服器與客戶端互動流程
Redis中的C/S模型 Redis底層還是基於網路請求的,對於單機資料庫而言,網路請求僅僅是在一臺機器上互動,即伺服器客戶端都在一臺計算機上 當在終端輸入redis-serve時,便啟動了一個Redis伺服器,隨後開始初始化內部資料,對於Redis而言包括
Dubbo學習筆記(一)—— 建立一個簡單的Dubbo入門專案演示HelloWorld
一、Dubbo簡介1、dubbo是什麼?▶ 一個分散式服務框架▶ 一個RPC遠端服務呼叫方案▶ 一個SOA服務治理方案2、dubbo架構圖3、節點說明Provider:暴露服務的服務提供方Consumer:呼叫遠端服務的服務消費方Registry:發現並註冊服務的服務註冊中心
SpringBoot學習-(一)如何在MyEclipse中建立SpringBoot專案
第一步: 右鍵,New選擇建立maven專案 第二步: 注意勾選create a simple project(skip archetype selection)//建立一個簡單的專案跳過原型選擇 第三步: groupid和artifa
react爬坑之路(一)--報錯output.path不是絕對路徑
bpa file 文件 開始 put pac 這就是 文件頭部 之前 之前,一直在糾結是學習angular好,學習vue好,還是學習react好,網上一搜索,也是各種對比,各種互噴,看過之後更糾結。就跟小時候一樣糾結長大了是上清華好,還是上北大好,最後證明我想多了。總之