兩年多前ReactJS剛出現的時候,Angular與Ember(加上更早期的Backbone)還是大型網站應用程式的開發主流。ReactJS的出現,引起許多人注意,當時我看到一篇文章很有意思:Removing User Interface Complexity, or Why React is Awesome,裡面提到了ReactJS與immediate-mode GUI的相似之處。那是我第一次知道immediate-mode GUI這檔事,也很佩服作者的洞察力。這兩年來,我發現很少人談論這件事,中文的文章更是沒有,所以我想花點時間跟大家介紹一下ReactJS與immediate-mode GUI的關係,因為我認為這是ReactJS的最重要特性。
我的GUI開發經驗
早在web出現之前,圖形化視窗作業系統(特別是Unix的X Window)已經存在很久了。我第一次接觸GUI程式設計,是1994年在Stanford唸研究所的時候,為了拿到研究助理獎學金補貼昂貴學費,而申請加入一間STeP實驗室打工。在那間實驗室,只有我一個碩士班學生,其他全都是博士班,所以我負責比較外圍的工作:用ML(一種functional programming language,影響了後來的Haskell、OCaml、Scala、Erlang等)開發X Window的UI元件,例如檔案對話盒等等。我的老闆Zohar Manna是個法國人,帶我的博班學長Nikolaj Bjorner是個瑞典人,那是一間很有趣的實驗室。但是我的開發工作就沒那麼有趣了,當學長們都在運用Zohar Manna的玄妙理論寫著各種神奇演算法的時候,我卻得經常跟X Window系統奮鬥,用高階的ML語言想辦法兜出低階的視窗元件,當然收穫也不少就是了。
1996年我回台灣工作,那時web剛出現,微軟的Windows 3.1、Windows 95還是應用程式的開發主流,大家都在微軟的視窗作業系統下討生活,我也不例外。這幾年的工作經驗,加上之前X Window的開發經歷,讓我對GUI程式設計有一定了解。基本上,GUI程式設計都是event-based,程式員先在畫面上把元件逐一加上去,然後用callback接收使用者的動作,再根據使用者動作以新增/修改/刪除畫面元件。比起撰寫資料結構及演算法,撰寫GUI程式是件繁瑣的差事,從功能表選單、快速鍵、radio button、check box、文字輸入、捲軸、按鈕等各種widget到layout,每個元件的狀態及元件之間的互動都得自己處理。例如使用者做了某個動作,可能觸發某些功能表選單disable、某些區塊訊息更新、某些按鈕enable、某些選項新增,而這些變更又視另一個元件的狀態(例如某個check box或捲軸位置)的讀取結果而定。只要介面稍微複雜一點,程式複雜度立刻瞬間飆升。
Immediate Mode GUI與Retained Mode GUI
我前面提到的文章,裡面有一段關於immediate-mode GUI的影片連結,主講者Casey Muratori長年在video game產業寫程式。這段40分鐘的影片是2005年錄的,我認為他講得很好,直指GUI程式設計複雜度的核心問題:
https://mollyrocket.com/861 (或前往YouTube直接觀看)
Screenshot by Arthur Liao @flickr
如果沒時間看影片,這裡有幾張介紹immediate-mode GUI的投影片可以參考:Immediate Mode Graphical User Interfaces
簡單講,一般的GUI程式設計都是retained mode,畫面上的GUI元件同時也是保存應用程式資料及狀態的地方;針對外部event的回應,程式員再逐一新增、修改或刪除畫面元件。相對於retained mode,Casey Muratori在影片中提倡的immediate mode則是將資料獨立於GUI元件之外,每次的畫面變動都根據應用程式資料重新繪製,不用考慮目前GUI元件的狀態為何。這種做法,大幅簡化了retained mode帶來的軟體複雜度,程式員只要專注在資料狀態的維護,並根據資料render畫面元件即可。
例如原本在retained mode之下,元件A1狀態的改變會觸發元件B、C和D的狀態改變;後來產品經理(或老闆)要求多加一個元件A2,它的狀態也會影響元件B、C和D,但影響方式則視元件A1的狀態而定。可以想像,當元件數量以線性增加時,元件之間互動的排列組合會呈指數飆升。相反的,在immediate mode之下,元件根據應用程式資料重新繪製,不用考慮彼此狀態及元件互動的排列組合,因此元件數量增加時,並不會大幅增加程式複雜度。
然而immediate mode的做法,意味著任何小變動都要重新render整個畫面,可能導致UI效能大幅下降。Casey Muratori在影片中介紹了immediate mode的概念,卻沒有提供相關的library,他是如何實做immediate mode GUI,我們不得而知。我已經很多年沒開發GUI程式了,對這個領域的現況所知有限,若有人知道更多訊息,歡迎分享。(聽說Qt QML很受歡迎,它是一種declarative UI語法,雖然不是immediate mode GUI,卻頗能解決問題。)
ReactJS與Immediate-mode GUI的相似之處
回到ReactJS。太陽底下沒有新鮮事,網頁應用程式開發者遇到的問題,許多年前的GUI開發者已經遇過了。網頁元件帶有retained mode的特性,例如jQuery讓網頁前端工程師可以自由操作網頁上的各種元件,UI互動不多時很好控制,互動一多,程式維護起來就很痛苦了。工程師要操縱數量不斷增長的元件,每一個都不能出錯,就像玩拋接把戲的街頭藝人一樣。
Photo credit: Magnus Hagdorn @flickr CC BY 2.0
ReactJS的一大特色,就是使用virtual DOM。所有React元件都先render成virtual DOM,由React幫你找出有變動的部份,再把這些變動反應到HTML DOM。ReactJS元件採用類似immediate-mode的方式撰寫,也就是每次render都根據元件的props/state資料重新繪製,不用考慮現有畫面元件的狀態。這樣一來,ReactJS開發者既享有immediate-mode的好處,又不用擔心UI效能的問題,可謂魚與熊掌兼得。在我看來,這是ReactJS最重要的特性,也是ReactJS成為開發複雜網站介面首選的原因!當然,開發大型應用程式,只用ReactJS是不夠的,必須搭配其它框架,像是Flux、Redux或FluentJS(順便廣告一下 :P)才行。
以上介紹,希望能讓大家對immediate-mode GUI有些了解,也讓還沒採用ReactJS的人,對ReactJS更有興趣。