日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Flutter React编程范式实践

發(fā)布時(shí)間:2025/3/19 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter React编程范式实践 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者:閑魚(yú)技術(shù)-匠修
Flutter Widget的設(shè)計(jì)靈感來(lái)源于React,是一款原生就立足于響應(yīng)式的UI框架。本文基于Flutter特點(diǎn),試圖結(jié)合閑魚(yú)在Flutter的工程應(yīng)用來(lái)談下我們對(duì)Flutter React編程范式的思考和踐行。

Reactive的誕生

談起UI總會(huì)講到MVC,它出現(xiàn)的時(shí)間很早,那時(shí)候還沒(méi)有普及現(xiàn)代GUI廣泛使用的事件驅(qū)動(dòng)(消息循環(huán))模型,所以很長(zhǎng)的時(shí)間內(nèi),MVC都在進(jìn)化,不斷的被重新定義。到現(xiàn)在MVC已經(jīng)是一個(gè)很寬泛的概念了。使用基礎(chǔ)的MVC作為框架來(lái)開(kāi)發(fā)容易出現(xiàn)模塊職責(zé)邊界模糊,邏輯調(diào)用方向混亂。GUI框架進(jìn)化后,將用戶事件的分發(fā)處理集成到了View模塊中,由此出現(xiàn)了MVP,MVP職責(zé)劃分較清晰,邏輯調(diào)用方向也比較好把握,但是很繁瑣,開(kāi)發(fā)效率不高。再隨著Web的發(fā)展,標(biāo)記語(yǔ)言被應(yīng)用于界面描述,開(kāi)始出現(xiàn)邏輯界面分離和無(wú)狀態(tài)化界面,MVVM應(yīng)運(yùn)而生。MVVM讓架構(gòu)層面來(lái)提供數(shù)據(jù)和View的雙向綁定,減輕了開(kāi)發(fā)工作,但有時(shí)候也帶來(lái)了一定程度的狀態(tài)混亂。函數(shù)式編程在近年被重新提起,并引發(fā)潮流,催生了響應(yīng)式界面開(kāi)發(fā),響應(yīng)式是對(duì)GUI事件驅(qū)動(dòng)模型的一種返璞歸真。

個(gè)人對(duì)前端架構(gòu)迭代的理解:

從迭代歷程上看,Model和View是兩個(gè)相對(duì)固定的角色,它們?nèi)菀桌斫?#xff0c;也能很好的確定職責(zé)邊界。如何去溝通Model和View是架構(gòu)設(shè)計(jì)的關(guān)鍵,響應(yīng)式的一般做法是讓Model回到最初的事件驅(qū)動(dòng),結(jié)合函數(shù)式的數(shù)據(jù)流來(lái)驅(qū)動(dòng)View刷新。這樣有比較清晰的角色劃分和簡(jiǎn)單易于理解的邏輯鏈接,能較好的統(tǒng)一編程模式。

Flutter的Reactive特性

通常GUI框架都有一些共同點(diǎn),比如View的樹(shù)形層級(jí),消息循環(huán),Vsync信號(hào)刷新等,Flutter也繼承這些經(jīng)典的設(shè)計(jì),但是Flutter并沒(méi)有使用標(biāo)記語(yǔ)言來(lái)描述界面(例如Web中的HTML,Android中的XML),這其中有Flutter立足于響應(yīng)式的初衷。Reactive是一款將事件數(shù)據(jù)流作為核心的開(kāi)發(fā)模型,UI框架會(huì)提供相應(yīng)的特性來(lái)提供更好的支持。

1.描述界面而不要操作界面

有一種說(shuō)法認(rèn)為函數(shù)式語(yǔ)言和命令式語(yǔ)言的不同在于命令式語(yǔ)言是給計(jì)算機(jī)下達(dá)指令而函數(shù)式語(yǔ)言是向計(jì)算機(jī)描述邏輯。這種思路在Flutter UI中得到了體現(xiàn)。Flutter不提倡去操作UI,它當(dāng)然也基本不會(huì)提供操作View的API,比如我們常見(jiàn)的類似TextView.setText(),Button.setOnClick()這種是不會(huì)有的。對(duì)界面的描述是可以數(shù)據(jù)化的(類似XML,JSON等),而對(duì)界面的操作是很難數(shù)據(jù)化的,這很重要,響應(yīng)式需要方便可持續(xù)的將數(shù)據(jù)映射成界面。

在Flutter中用Widget來(lái)描述界面,Widget只是View的“配置信息”,編寫(xiě)的時(shí)候利用Dart語(yǔ)言一些聲明式特性來(lái)得到類似結(jié)構(gòu)化標(biāo)記語(yǔ)言的可讀性。不論Stateless Widget 還是 Stateful Widget都是不可變的(immutable),其中的成員變量也應(yīng)該都是final的,也就是說(shuō),Widget是“只讀”的。Widget是數(shù)據(jù)的映射,當(dāng)數(shù)據(jù)改變的時(shí)候,我們需要重新創(chuàng)建Widget去更新界面,這意味著Widget會(huì)創(chuàng)建銷毀的非常頻繁,不過(guò)Flutter使用的Dart虛擬機(jī)能高效的處理這種短周期的輕量對(duì)象。

這種設(shè)計(jì)思路對(duì)剛接觸的開(kāi)發(fā)者可能有些不習(xí)慣,我們可以借助開(kāi)發(fā)Android中的ListView(iOS中的TableView)來(lái)理解:我們通常先準(zhǔn)備好一個(gè)數(shù)據(jù)List,然后實(shí)現(xiàn)一個(gè)Adapter來(lái)將List中的items映射成一個(gè)個(gè)itemView,最后將List和Adapter設(shè)置給ListView。這樣當(dāng)我們改變List中的數(shù)據(jù),ListView就會(huì)相應(yīng)的刷新View。Flutter類似,我們準(zhǔn)備好Widgets(只不過(guò)Widget的“容器”是Tree而不是List),Flutter會(huì)提供Adapter(RenderObjectToWidgetAdapter)將其映射成渲染用的RenderObject,當(dāng)Widget更新時(shí)就會(huì)刷新界面。

另外,Widget也能通過(guò)設(shè)置Key來(lái)緩存復(fù)用,在類似ListView的場(chǎng)景中,Item Widget的復(fù)用是很有收益的。

2.基于共同祖先通信

在我們國(guó)家,如果你想和別人溝通上拉近距離,有時(shí)候會(huì)進(jìn)入到類似“我們500年前是一家”的這種語(yǔ)境中。在Flutter中,如果兩個(gè)組件要通信,也是去找祖先(當(dāng)然,也有可能兩個(gè)組件本身就有遺傳關(guān)系),Flutter把它描述成“數(shù)據(jù)上行,通知下行”。

但是,在一個(gè)非常復(fù)雜的樹(shù)形層級(jí)中,要找到某位“祖先”并不是很容易的事情,而且性能也不好。Flutter為此做了優(yōu)化,提供了InheritedWidget,“祖先”Widget繼承該類型后,child可以通過(guò)BuildContext中提供的inheritFromWidgetOfExactType方法方便的找到在層級(jí)中離的最近的那位“祖先”。該方法做了優(yōu)化,效率很高,并且可以讓child和“祖先”建立依賴關(guān)系,方便做刷新。

Flutter中并沒(méi)有提倡類似controller的概念(像Android中的Activity,iOS中的ViewController),本身View是不可操作的,controller也就失去了意義。那么,組件之間的通信就必須在View層“自力更生”了。

3.函數(shù)式數(shù)據(jù)流

這肯定不是Flutter才有的,要想把響應(yīng)式實(shí)現(xiàn)的簡(jiǎn)潔優(yōu)雅,就要利用好語(yǔ)言的函數(shù)式特性。Flutter的亮點(diǎn)是它使用的Dart語(yǔ)言能把這件事情變的很輕量,你基本不需要引入什么第三方庫(kù)就能做到(不過(guò)確實(shí)有RxDart庫(kù),但感覺(jué)只是做了額外的增強(qiáng)),而且明顯語(yǔ)言Api的設(shè)計(jì)也往這個(gè)方向上做了優(yōu)化,非常方便。具體可以看看Stream和RxDart。

基于React的框架實(shí)踐

統(tǒng)一狀態(tài)管理和單向數(shù)據(jù)流

通過(guò)React的實(shí)踐,響應(yīng)式可以很好的解決數(shù)據(jù)到界面的更新,而且效率也不錯(cuò)。但是自身對(duì)數(shù)據(jù)狀態(tài)的管理不足,React官方提出了Flux,而在面對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景時(shí),Flutter官方也是推薦Redux架構(gòu),我們也是根據(jù)這一思路搭建的框架。

首先是業(yè)務(wù)邏輯和界面分離,界面是無(wú)狀態(tài)(Stateless)的,我們也正在嘗試自動(dòng)化的方法直接生成界面代碼,所以Widget中是不會(huì)有業(yè)務(wù)邏輯代碼的。當(dāng)我們把一個(gè)能描述當(dāng)前界面的數(shù)據(jù)(State)交給View層時(shí),界面就應(yīng)該能正常展示。用戶和界面交互會(huì)產(chǎn)生Action,Action代表了用戶交互的意圖,Action可以攜帶信息(比如用戶使用輸入留言,Action中就應(yīng)該攜帶用戶留言的內(nèi)容信息)。Action會(huì)輸入給Store,Store會(huì)通過(guò)注冊(cè)的Interrupters對(duì)Action做前期攔截處理,可以通過(guò)Interrupter截?cái)rAction,也可以把一個(gè)Action重新改寫(xiě)成另外的Action。Store然后收集相應(yīng)綁定的Reducers對(duì)Action做一次reduce操作,產(chǎn)生新的State,并通知界面刷新。

通常我們?cè)趧?chuàng)建Store的時(shí)候就組冊(cè)好Reducer和Interrupter:

Store<PublishState> buildPublishStore(String itemId) {//設(shè)置狀態(tài)初始值PublishState initState = new PublishState();initState.itemId = itemId;initState.isLoading = true;//創(chuàng)建Reducer和對(duì)應(yīng)Action的綁定var reducerBinder = ActionBinder.reducerBinder<PublishState>()..bind(PublishAction.DETAIL_LOAD_COMPLETED, _loadCompletedReducer)..bind(PublishAction.DELETE_IMAGE, _delImageReducer)..bind(PublishAction.ADD_IMAGE, _addImageReducer);//創(chuàng)建Interrupter和對(duì)應(yīng)Action的綁定var interrupterBinder = ActionBinder.interrupterBinder<PublishState>()..bind(PublishAction.LOAD_DETAIL, _loadDataInterrupter)..bind(PublishAction.ADD_IMAGE, UploadInterruper.imageUploadInterrupter);//創(chuàng)建Store return new CommonStore<PublishState>(name: 'Publish',initValue: initState,reducer: reducerBinder,interrupter: interrupterBinder); }

Reducer中就是處理用戶交互時(shí)產(chǎn)生的Action的邏輯代碼,接收3個(gè)參數(shù),一個(gè)是執(zhí)行上下文,一個(gè)要處理的Action,一個(gè)是當(dāng)前的State,處理結(jié)束后必須返回新的State。函數(shù)式理想的Reducer應(yīng)該是一個(gè)無(wú)副作用的純函數(shù),顯然我們不應(yīng)該在Reducer中去訪問(wèn)或者改變?nèi)钟虻淖兞?#xff0c;但有時(shí)候我們會(huì)對(duì)前面的計(jì)算結(jié)果有依賴,這時(shí)可以將一些運(yùn)行時(shí)數(shù)據(jù)寄存在ReduceContext中。Reducer中不應(yīng)該有異步邏輯,因?yàn)镾tore做Reduce操作是同步的,產(chǎn)生新State后會(huì)立即通知界面刷新,而異步產(chǎn)生對(duì)State的更新并不會(huì)觸發(fā)刷新。

PublishState _delImageReducer(ReduceContext<PublishState> ctx, Action action, PublishState state) {int index = action.args.deleteId;state.imageUplads.removeAt(index);return state; }

Interrupter形式上和Reducer類似,不同的是里面可以做異步的邏輯處理,比如網(wǎng)絡(luò)請(qǐng)求就應(yīng)該放在Interrupter中實(shí)現(xiàn)。

*為什么會(huì)有Interrupter呢?換一個(gè)角度,我們可以把整個(gè)Store看成一個(gè)函數(shù),輸入是Action,輸出的是State。函數(shù)會(huì)有副作用,有時(shí)我們輸入?yún)?shù)并不一定得會(huì)相應(yīng)有輸出,比如日志函數(shù)( void log(String) ),我們輸入String只會(huì)在標(biāo)準(zhǔn)輸出上打印一個(gè)字符串,log函數(shù)不會(huì)有返回值。同樣,對(duì)Store來(lái)說(shuō),也不是所有的Action都要去改變State,用戶有時(shí)候觸發(fā)Action只要想讓手機(jī)震動(dòng)下而已,并不會(huì)觸發(fā)界面更新。所以,Interrupter就是Store用來(lái)處理副作用的。

///截?cái)r一個(gè)網(wǎng)絡(luò)請(qǐng)求的Action,并在執(zhí)行請(qǐng)求網(wǎng)絡(luò)后發(fā)出新Actionbool _onMtopReq(InterrupterContext<S> ctx, Action action) {NetService.requestLight(api: action.args.api,version: action.args.ver,params: action.args.params,success: (data) {ctx.store.dispatch(Action.obtain(Common.MTOP_RESPONSE)..args.mtopResult = 'success'..args.data = data);},failed: (code, msg) {ctx.store.dispatch(Action.obtain(Common.MTOP_RESPONSE)..args.mtopResult = 'failed'..args.code = code..args.msg = msg);});return true;}

通常我們會(huì)讓一個(gè)界面根部的InheritedWidget來(lái)持有Store,這樣界面上的任何Widget
都能方便的訪問(wèn)到Store,并和Store建立聯(lián)系。這種做法可以參考redux_demo,再此不詳細(xì)展開(kāi)。

最后簡(jiǎn)單的說(shuō)說(shuō)Store的實(shí)現(xiàn),Store能夠接收Action,然后執(zhí)行reduce,最后向widget提供數(shù)據(jù)源。Widget可以基于提供的數(shù)據(jù)源建立數(shù)據(jù)流,響應(yīng)數(shù)據(jù)變更來(lái)刷新界面。這其中最核心的就是Dart的Stream。

......//創(chuàng)建分發(fā)數(shù)據(jù)的Stream_changeController = new StreamController.broadcast(sync: false);//創(chuàng)建接收Action的Stream_dispatchController = new StreamController.broadcast(sync: false);//設(shè)置響應(yīng)Action的函數(shù)_dispatchController.stream.listen((action) {_handleAction(action);});......//向Store中分發(fā)Action void dispatch(Action action) {_dispatchController.add(action);}//Store向外提供的數(shù)據(jù)源Stream<State> get onChange => _changeController.stream;

Store中最核心的對(duì)Action進(jìn)行reduce操作:

//收集該Action綁定的Reducerfinal List<ReduceContext<State>> reducers = _reducers.values.where((ctx) => ctx._handleWhats.any((what) => what == action.what)).toList();//執(zhí)行reduceBox<Action, State> box = new Box<Action, State>(action, _state);box = reducers.fold(box, (box, reducer) {box.state = reducer._onReduce(box.action, box.state);return box;}); //觸發(fā)更新_state = box.state;_changeController.add(_state);

Widget基于Store暴露的數(shù)據(jù)源建立數(shù)據(jù)流:

store.onChange//將Store中的數(shù)據(jù)轉(zhuǎn)換成Widget需要的數(shù)據(jù).map((state) => widget.converter(state)) //比較前一次數(shù)據(jù),如果想等則不用更新界面.where((value) => (value != latestValue))//更新界面.listen((value){...setState()...})

組件化的擴(kuò)展

我們?cè)跇I(yè)務(wù)開(kāi)發(fā)中發(fā)現(xiàn),有時(shí)候一個(gè)頁(yè)面一個(gè)Store會(huì)帶來(lái)組件復(fù)用上的不方便,比如視頻播放組件是一個(gè)邏輯比較內(nèi)聚的組件,如果把它的reducer都集中放在頁(yè)面的Store中那么別的頁(yè)面想要復(fù)用這個(gè)開(kāi)發(fā)好的視頻組件就不方便了,這時(shí)候視頻組件可能需要一個(gè)獨(dú)立的Store來(lái)存放視頻播放相關(guān)的邏輯。我們遵循Flutter組件通信方法,將框架擴(kuò)展為允許存在多個(gè)Store,并且做到對(duì)Widget開(kāi)發(fā)無(wú)感知。

Widget只能感知離它最近的Store持有者,該Store會(huì)向更高層級(jí)Store轉(zhuǎn)發(fā)Action,同時(shí)接收來(lái)自更高層級(jí)Store的數(shù)據(jù)變更并通知Widget。

延展討論

相對(duì)目前流行的MVVM框架(Vue,Angular)能夠細(xì)粒度的綁定數(shù)據(jù),并實(shí)現(xiàn)界面的最小化刷新,Flutter上面還沒(méi)有找到很好的辦法能夠在框架內(nèi)自動(dòng)實(shí)現(xiàn),目前只能依賴開(kāi)發(fā)者去手動(dòng)處理。這不免會(huì)降低開(kāi)發(fā)效率,拉低開(kāi)發(fā)體驗(yàn),我們也在探索更好的方法,如果感興趣或者有好的解決思路,歡迎和我們交流。

當(dāng)遇到狀態(tài)復(fù)雜頁(yè)面(多動(dòng)畫(huà),多view聯(lián)動(dòng))時(shí),Store中應(yīng)該要提供相關(guān)工具或機(jī)制來(lái)管理復(fù)雜的狀態(tài)來(lái)提高開(kāi)發(fā)效率,狀態(tài)機(jī)是個(gè)可選的方案之一。如果有在Dart下優(yōu)雅的狀態(tài)機(jī)框架實(shí)現(xiàn)或思路,請(qǐng)務(wù)必和我們分享一下。

最后,閑魚(yú)技術(shù)團(tuán)隊(duì)廣招各類方向的達(dá)人,無(wú)論你是精通移動(dòng)端,前端,后臺(tái),還是機(jī)器學(xué)習(xí),音視頻,自動(dòng)化測(cè)試等,都?xì)g迎投遞簡(jiǎn)歷加入我們,一同用技術(shù)改善生活!
簡(jiǎn)歷投遞:guicai.gxy@alibaba-inc.com

總結(jié)

以上是生活随笔為你收集整理的Flutter React编程范式实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。