帶你重新“玩轉(zhuǎn)”Flutter
Flutter作為一項(xiàng)已經(jīng)逐漸進(jìn)入規(guī)模化實(shí)踐的技術(shù),它的價值已經(jīng)初步獲得認(rèn)可,后續(xù)應(yīng)該有不錯的生命力。作為較早期的Flutter實(shí)踐者,我一直在思考Flutter的技術(shù)價值以及如何釋放這些價值,本篇嘗試從一個新的視角去結(jié)構(gòu)化的梳理Flutter的技術(shù)價值并做對應(yīng)的應(yīng)用分析。
這里不會涉及到Flutter具體領(lǐng)域的技術(shù)點(diǎn),但是會結(jié)合我們團(tuán)隊(duì)過去的探索實(shí)踐,在技術(shù)使用策略的層面做一些總結(jié),希望能幫助到小伙伴們在開發(fā)實(shí)踐中思考提煉,抬頭看路,仰望星空,找到未來的創(chuàng)新方向。
前端有些啥問題
要溯源Flutter, 就得從前端說起了。這里的前端是相對于后端的概念,大概泛指通過圖形界面實(shí)現(xiàn)用戶交互的終端技術(shù)。這個領(lǐng)域一直很有活力,伴隨著互聯(lián)網(wǎng)一路走來,很多新思路和新技術(shù)都有點(diǎn)眼花繚亂:上古的MVC已經(jīng)不大提起了,老一輩的MVP,MVVM也逐漸暗淡,新一輩的Vue,Angular,React方興未艾,還有Redux, Mobx, Hooks推波助瀾。這些技術(shù)都像是一個個有感情的小生命,有的熱情,有的文藝,有的高冷,十分熱鬧。也許你會想:它們都在說個啥?它們都想解決一個啥問題?
節(jié)點(diǎn),連接和網(wǎng)絡(luò)
互聯(lián)網(wǎng)是這一切存在的大背景,互聯(lián)網(wǎng)有3個大要素:節(jié)點(diǎn),連接和網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)。每一個要素的進(jìn)化都會給前端帶來升級和變革:從PC互聯(lián)網(wǎng)到移動互聯(lián)網(wǎng)再到物聯(lián)網(wǎng)是節(jié)點(diǎn)在進(jìn)化;充滿想象的5G時代是連接在進(jìn)化;從中心化到分布式是網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)在進(jìn)化。前端的使命是讓用戶(人)高效的嵌入到網(wǎng)絡(luò)之中,讓人和設(shè)備融合為一個有生命的網(wǎng)絡(luò)“節(jié)點(diǎn)”。

PS:網(wǎng)絡(luò)模型可以適配到前端的很多場景,里面的“節(jié)點(diǎn)”可以代指手機(jī)設(shè)備,應(yīng)用程序,進(jìn)程,頁面,甚至組件。
節(jié)點(diǎn)與前端技術(shù)
前端技術(shù)是用戶(人)與設(shè)備的粘合劑,讓二者有機(jī)的構(gòu)成一個網(wǎng)絡(luò)節(jié)點(diǎn)。我嘗試剖析下前端技術(shù)在節(jié)點(diǎn)中的形態(tài):首先它需要提供界面給用戶,并響應(yīng)用戶的交互;需要管理和遠(yuǎn)端節(jié)點(diǎn)的通信,交換信息;還需要管理當(dāng)前節(jié)點(diǎn)的信息狀態(tài);最后,為了方便的嵌入到網(wǎng)絡(luò)中,節(jié)點(diǎn)需要一個友好的“外觀”,就是生命周期。生命周期描述了節(jié)點(diǎn)的誕生,消亡,所擁有的權(quán)益,和所承擔(dān)的責(zé)任。

PS:這個分治模型也具有一定的通用性,可以適用在應(yīng)用程序,頁面,或者組件。
前端要解決的問題
當(dāng)然,前端技術(shù)作為一種軟件技術(shù),在日常的工程開發(fā)中也需要基礎(chǔ)的構(gòu)建部署支撐。那么,綜合前面的討論,前端要解決的基礎(chǔ)要素問題有:
•遠(yuǎn)端通信
•界面管理
•生命周期
•狀態(tài)管理
•構(gòu)建部署
這里面生命周期管理是一個比較“隱形”的問題,它其實(shí)就是構(gòu)建“外觀”描述,也就是“組件化”。這些基礎(chǔ)要素問題的結(jié)構(gòu)關(guān)系大概是:

這幾個基礎(chǔ)要素問題的解決往往不能孤立的進(jìn)行,在設(shè)計解決方案的時候需要彼此配合。前端領(lǐng)域的技術(shù)方案通常都會合并解決其中的幾個問題,比如:React解決界面管理和生命周期;Vue解決了界面管理,狀態(tài)管理,和生命周期;Redux專注解決狀態(tài)管理;GraphQL,BFF,Serverless解決遠(yuǎn)端通信;webpack解決構(gòu)建部署等等...
那么,F(xiàn)lutter解決了什么問題?或者,F(xiàn)lutter可以解決哪些問題呢?
Flutter都有些啥
有趣的技術(shù)方案應(yīng)該有自己旗幟鮮明的個性,做架構(gòu)設(shè)計往往是遇到了獨(dú)特的問題或者發(fā)現(xiàn)了更好的工具。因此,使用Flutter做解決方案的時候,應(yīng)該梳理下Flutter都帶來了些什么。一般來說,主要有Dart語言,Dart運(yùn)行時(VM),GUI框架,和相關(guān)的開發(fā)構(gòu)建工具。
Dart語言
Dart語言不好說有多優(yōu)秀,總的來說是一門工程“友好”的現(xiàn)代語言。也許感覺平淡無奇,但官方在介紹Flutter的時候,還略有“驕傲”的展示了它的包容性:它能較為原生的支持聲明式,過程式,面向?qū)ο螅瘮?shù)式,和響應(yīng)式等業(yè)務(wù)場景,它給技術(shù)方案實(shí)現(xiàn)提供了很自由的范式空間,這是值得發(fā)掘和嘗試的。結(jié)合我自己的實(shí)踐,有幾個點(diǎn)值得一說:
•支持類似“協(xié)程”處理(async/await/yield):Dart是單線程的,但是支持異步。這一點(diǎn)需要在使用中去體會,不展開說,理解深了以后,可能對很多問題都有新解法。
•提供Stream:當(dāng)你要進(jìn)階學(xué)習(xí)Flutter的時候,很多地方能見到它。它能做流式數(shù)據(jù)處理,函數(shù)組合,傳輸控制,屬于進(jìn)階必知必會。
•mixin特性:這是很有用的特性,尤其在架構(gòu)設(shè)計方面。它提供了新的組合方式,在做功能組合的時候,Dart可以使用對象注入,或者高階函數(shù),或者mixin。
Dart-Runtime / VM
當(dāng)你引入Flutter,你就有一個Dart-Runtime啦。當(dāng)然,它本來的意義是支持Flutter運(yùn)行的,然而就像JS的V8引擎一樣,本來是用來支持H5頁面的,后面的腦洞就越開越大了。當(dāng)然Dart沒有JS的動態(tài)能力(特殊的場合也可以),但它是跨端的,而且aot性能有保障,發(fā)掘空間較大,還能同時覆蓋幾乎所有場景,手機(jī)App開發(fā),PC的構(gòu)建部署,服務(wù)端,甚至serverless運(yùn)行時。
Flutter應(yīng)用開發(fā)框架
Flutter帶來了高性能的跨平臺GUI Framework,這沒啥可說的,用就是了!但如果已經(jīng)有了自己的開發(fā)生態(tài),舍棄成本太大,又想“借用”下Flutter的技術(shù)優(yōu)勢,大概有幾種方案:
•語言轉(zhuǎn)換,基本上就是把其他語言的布局結(jié)果交給Flutter Framework。
•中層Framework介入,選擇一棵樹介入,這樣對接面會小一些。
•底層Framework替換,整個替換掉Flutter Framework, 直接使用底層的Engine。

當(dāng)然,還有一種方式就是混合開發(fā)了,需要實(shí)現(xiàn)混合棧管理,而且在1.22版本以后,F(xiàn)lutter升級了Navigator組件,提供了Navigator 2.0,這對混合開發(fā)是一個利好。
開發(fā)構(gòu)建工具
值得一提的是HotReload,誰用誰知道。熱部署不僅界面開發(fā)需要,這是個通用需求,如果在服務(wù)端通過Dart實(shí)現(xiàn)一個,也是極好的。
Flutter的構(gòu)建工具并無太多亮點(diǎn),而且因?yàn)镈art語言在Flutter中不支持反射(產(chǎn)物太大,增加包體積),業(yè)務(wù)架構(gòu)實(shí)現(xiàn)上很多需要依賴于編譯時處理的技術(shù),這對構(gòu)建系統(tǒng)的能力有較大依賴的。換一個角度,因?yàn)檫@一領(lǐng)域尚沒有成熟方案,加上Dart的工程“友好”,可能實(shí)現(xiàn)由一種語言來統(tǒng)一前端開發(fā),后端開發(fā),以及構(gòu)建和部署,真做到了會很酷!
Flutter可以有些啥玩法
討論前端要解決的幾個基礎(chǔ)要素問題,也討論了Flutter帶來的技術(shù)工具,結(jié)合這兩點(diǎn),看看利用Flutter都能做些啥。
遠(yuǎn)端通信
“遠(yuǎn)端通信”是一個做了抽象的概念,具體形式會根據(jù)“節(jié)點(diǎn)”的定義不同而不同。可以是指物理設(shè)備之間的通信(手機(jī)與服務(wù)器),也可以指模塊之間(頁面與另一個頁面)的通信,還可以指組件之間(兩個StatefulWidget之間)的通信。因?yàn)樵贔lutter中“一切皆是Widget”,所以簡化的看,F(xiàn)lutter里面大概分為兩種情形:面向服務(wù)器的通信和組件(Widget)之間的通信。
•面向服務(wù)器的通信:這方面有傳統(tǒng)的Restful,GraphQL,還有興起于“云原生”概念的Serverless。借助于Dart的能力延伸,Dart-Runtime可以成為Serverless的可選容器,那么前后兩端都可以使用Dart來開發(fā),如果再用Dart補(bǔ)足中間的構(gòu)建部署pipeline,那么可以搭建出一個很“云原生”概念的應(yīng)用程序開發(fā)流(dev flow):

Flutter的跨端能力結(jié)合云原生的彈性部署,前后端的實(shí)現(xiàn)可以放到一起,它們之間的調(diào)用也變得簡單自然,有理由相信這會是一個高效的開發(fā)方式。閑魚已經(jīng)做了前期的探索,并在業(yè)務(wù)中嘗試落地。
此外,GraphQL與Flutter也是值得嘗試的組合,GraphQL和React范式在理念上很對味口,但是我們并沒有嘗試過。
•組件之間的通信:Flutter提供的基礎(chǔ)業(yè)務(wù)編程組件是StatefulWidget和StatelessWidget,它們是按樹形結(jié)構(gòu)來組織的,并提供了InheritedWidget來支持它們之間的通信,在通信方式可以總結(jié)出3種:
-
-
Notify型:通知/監(jiān)聽模式,F(xiàn)lutter提供了ValueNotifier和ChangeNotifier, 簡單方便,適用于輕量信息通信,參見Provider
-
Invoke型:接口調(diào)用模式,類似輕量RPC方式,在Flutter的應(yīng)用架構(gòu)設(shè)計中竟然很少見到。它確實(shí)有些重,但是有前面的模式?jīng)]有的優(yōu)勢:它是雙向的。
-
Transmission型:數(shù)據(jù)傳輸模式,Dart提供了Stream來支持這種模式。使用靈活,擴(kuò)展方便,幾乎是框架設(shè)計必備,參見BLoC。
-

狀態(tài)管理
狀態(tài)管理是個大問題,它由兩個元素構(gòu)成:狀態(tài)和處理狀態(tài)的邏輯。在代碼實(shí)現(xiàn)上,狀態(tài)就是數(shù)據(jù),邏輯就是函數(shù)。當(dāng)它們變得復(fù)雜的時候,解決的辦法其實(shí)就是拆分,然后再合理的組合。我嘗試從狀態(tài)和邏輯的拆分選擇上來列舉所有可能的解法:
•狀態(tài)不拆,拆分邏輯: 這種做法的特點(diǎn)全局共用一個狀態(tài)結(jié)構(gòu)體對象,所有狀態(tài)全部放在這個對象中,叫做“統(tǒng)一狀態(tài)管理”。只有一個數(shù)據(jù)對象,就消除了各個處理邏輯之間信息共享問題,邏輯內(nèi)部沒有狀態(tài),變得非常的純粹(比如純函數(shù)來實(shí)現(xiàn)),再將邏輯以合適的方式組合成一個整體,實(shí)現(xiàn)上界限清晰,簡單優(yōu)雅,代表的方案就是Redux。
如果采用這種設(shè)計方案,推薦使用函數(shù)式風(fēng)格來實(shí)現(xiàn),這倒不是因?yàn)楹瘮?shù)式“更高效”,“更優(yōu)雅”之類的,而是它與函數(shù)式的思路十分的契合,在實(shí)現(xiàn)上更容易把握住思路。用面向?qū)ο髞韺?shí)現(xiàn)也并沒有問題,最后的效果取決于你所面臨的工程環(huán)境和用戶。
這種狀態(tài)管理好處顯而易見:一處狀態(tài)發(fā)生改變,不需要到處發(fā)事件同步。但是如果用在復(fù)雜大業(yè)務(wù)上面(比如一個有數(shù)十個頁面流程的業(yè)務(wù)產(chǎn)品),狀態(tài)必定是復(fù)雜的,狀態(tài)結(jié)構(gòu)體必然是龐大的。Redux的方式很好的管理了邏輯,如果要管理一個統(tǒng)一龐大的狀態(tài)數(shù)據(jù),也許內(nèi)存級別的SQL是個不錯的主意,GraphQL-Client給了我們啟發(fā),我們也正在嘗試實(shí)踐。

•邏輯不拆,拆分狀態(tài): 我把這種叫做“步進(jìn)狀態(tài)管理”,實(shí)際上這類似于狀態(tài)機(jī)模式。但如果要真正使用,狀態(tài)必須是有限的,而且不能經(jīng)常變動。這與互聯(lián)網(wǎng)持續(xù)多變的業(yè)務(wù)需求實(shí)際是不符合的,所以采用這種方式的設(shè)計幾乎沒有。但在一些很嚴(yán)肅的業(yè)務(wù)場景中,比如交易流程,一旦定下來就不容易變動。這時,步進(jìn)狀態(tài)管理就很合適了。

•同時拆分邏輯和狀態(tài): 這是容易想到方案,在更細(xì)的粒度上,將狀態(tài)和它對應(yīng)的處理邏輯拆分打包,變成更小的域(scope),然后統(tǒng)一協(xié)調(diào)這些子域(subModel),我把這種叫做“組合狀態(tài)管理”。進(jìn)一步的方案可以統(tǒng)一定義subModel的基礎(chǔ)行為,然后引入調(diào)度器來協(xié)調(diào)管理它們,subModel之間還可以共享上下文來共享信息等等。
這種思路形式自由,可以采用的實(shí)現(xiàn)方式很多,經(jīng)典的面向?qū)ο螽?dāng)然不在話下,scoped_model采用的就是這種思路,scoped_model實(shí)現(xiàn)的簡單易懂,同時能力也比較有限。當(dāng)然也可以采用函數(shù)式的方式,高階函數(shù)+閉包也能很好的實(shí)現(xiàn),但是圈內(nèi)沒有看到有相關(guān)的設(shè)計實(shí)現(xiàn)。還有一點(diǎn),Dart提供了Mixin特性,通過這個特性,可以得到更簡潔的實(shí)現(xiàn)方案。比如,可以沉淀很多特定的Model,然后通過with選擇組合到業(yè)務(wù)Model中來(參考Flutter Framework中WidgetsBinding的實(shí)現(xiàn))。目前flutter-hook在做這方面的探索,flutter-hook看起來有些迷惑,其核心就是利用了Dart的Mixin特性來組合狀態(tài)。

界面管理
Flutter使用了響應(yīng)式UI,目的就是讓業(yè)務(wù)開發(fā)減少界面的管理工作,只要提供好頁面“描述”就行了。雖然Flutter內(nèi)建了類似Virtual Dom的Diff機(jī)制,但是,做這個Diff也是要費(fèi)性能的,如果我們在框架設(shè)計上能內(nèi)建的把Diff工作提前到數(shù)據(jù)層,是不是可以提升性能呢?

我們嘗試了幾種方法,受制于在Flutter中Dart不能反射,效果不理想,也許后續(xù)生態(tài)完善之后會有好的解法。
在UI開發(fā)中,F(xiàn)lutter一直有一個隱隱的聲音就是動態(tài)化,官方好像是“忽略”的,這里也不談了。
生命周期
Flutter通過StatefulWidget給業(yè)務(wù)開發(fā)提供了基礎(chǔ)的生命周期管理。組件生命周期的設(shè)計是隨著業(yè)務(wù)場景的不同而不同的,我自己的理解,設(shè)計生命周期要從兩點(diǎn)出發(fā):一是組件從誕生到消亡的時間線,二是組件在場景中所擁有的權(quán)益和所承擔(dān)的責(zé)任。生命周期的擴(kuò)展原點(diǎn)是StatefulWidget,因業(yè)務(wù)場景不同而擴(kuò)展不同,很難展開講。
舉個例子,如果使用原生Flutter開發(fā)(或者叫純Flutter開發(fā)),StatefulWidget所提供的生命周期是足夠的。但是如果要做混合開發(fā),引入混合棧后,顯然就不夠用了。這時候就需要提供新的組件來擴(kuò)展生命周期,更好的滿足混合場景的開發(fā)。當(dāng)時我在做混合棧的時候并沒有能理解到這一點(diǎn)。
結(jié)語
本篇從分析前端開發(fā)需要面對的幾個基礎(chǔ)核心問題入手,結(jié)合Flutter帶來的技術(shù)工具,嘗試結(jié)構(gòu)化的分析Flutter在業(yè)務(wù)開發(fā)中可能的技術(shù)選擇和探索方向。當(dāng)然,技術(shù)同學(xué)既要仰望星空,還需要腳下看路,對于Flutter開發(fā)中的熱點(diǎn)技術(shù)問題,歡迎關(guān)注閑魚技術(shù)公眾號的其他文章,或者加入我們,和閑魚一起做一點(diǎn)不一樣的技術(shù)!