您好,登錄后才能下訂單哦!
這篇文章主要介紹“Flutter React編程的方法是什么”,在日常操作中,相信很多人在Flutter React編程的方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Flutter React編程的方法是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
Reactive的誕生
談起UI總會講到MVC,它出現(xiàn)的時間很早,那時候還沒有普及現(xiàn)代GUI廣泛使用的事件驅(qū)動(消息循環(huán))模型,所以很長的時間內(nèi),MVC都在進化,不斷的被重新定義。到現(xiàn)在MVC已經(jīng)是一個很寬泛的概念了。使用基礎(chǔ)的MVC作為框架來開發(fā)容易出現(xiàn)模塊職責(zé)邊界模糊,邏輯調(diào)用方向混亂。GUI框架進化后,將用戶事件的分發(fā)處理集成到了View模塊中,由此出現(xiàn)了MVP,MVP職責(zé)劃分較清晰,邏輯調(diào)用方向也比較好把握,但是很繁瑣,開發(fā)效率不高。再隨著Web的發(fā)展,標(biāo)記語言被應(yīng)用于界面描述,開始出現(xiàn)邏輯界面分離和無狀態(tài)化界面,MVVM應(yīng)運而生。MVVM讓架構(gòu)層面來提供數(shù)據(jù)和View的雙向綁定,減輕了開發(fā)工作,但有時候也帶來了一定程度的狀態(tài)混亂。函數(shù)式編程在近年被重新提起,并引發(fā)潮流,催生了響應(yīng)式界面開發(fā),響應(yīng)式是對GUI事件驅(qū)動模型的一種返璞歸真。
個人對前端架構(gòu)迭代的理解:
從迭代歷程上看,Model和View是兩個相對固定的角色,它們?nèi)菀桌斫?,也能很好的確定職責(zé)邊界。如何去溝通Model和View是架構(gòu)設(shè)計的關(guān)鍵,響應(yīng)式的一般做法是讓Model回到最初的事件驅(qū)動,結(jié)合函數(shù)式的數(shù)據(jù)流來驅(qū)動View刷新。這樣有比較清晰的角色劃分和簡單易于理解的邏輯鏈接,能較好的統(tǒng)一編程模式。
Flutter的Reactive特性
通常GUI框架都有一些共同點,比如View的樹形層級,消息循環(huán),Vsync信號刷新等,F(xiàn)lutter也繼承這些經(jīng)典的設(shè)計,但是Flutter并沒有使用標(biāo)記語言來描述界面(例如Web中的HTML,Android中的XML),這其中有Flutter立足于響應(yīng)式的初衷。Reactive是一款將事件數(shù)據(jù)流作為核心的開發(fā)模型,UI框架會提供相應(yīng)的特性來提供更好的支持。
1.描述界面而不要操作界面
有一種說法認為函數(shù)式語言和命令式語言的不同在于命令式語言是給計算機下達指令而函數(shù)式語言是向計算機描述邏輯。這種思路在Flutter UI中得到了體現(xiàn)。Flutter不提倡去操作UI,它當(dāng)然也基本不會提供操作View的API,比如我們常見的類似TextView.setText(),Button.setOnClick()這種是不會有的。對界面的描述是可以數(shù)據(jù)化的(類似XML,JSON等),而對界面的操作是很難數(shù)據(jù)化的,這很重要,響應(yīng)式需要方便可持續(xù)的將數(shù)據(jù)映射成界面。
在Flutter中用Widget來描述界面,Widget只是View的“配置信息”,編寫的時候利用Dart語言一些聲明式特性來得到類似結(jié)構(gòu)化標(biāo)記語言的可讀性。不論Stateless Widget 還是 Stateful Widget都是不可變的(immutable),其中的成員變量也應(yīng)該都是final的,也就是說,Widget是“只讀”的。Widget是數(shù)據(jù)的映射,當(dāng)數(shù)據(jù)改變的時候,我們需要重新創(chuàng)建Widget去更新界面,這意味著Widget會創(chuàng)建銷毀的非常頻繁,不過Flutter使用的Dart虛擬機能高效的處理這種短周期的輕量對象。
這種設(shè)計思路對剛接觸的開發(fā)者可能有些不習(xí)慣,我們可以借助開發(fā)Android中的ListView(iOS中的TableView)來理解:我們通常先準(zhǔn)備好一個數(shù)據(jù)List,然后實現(xiàn)一個Adapter來將List中的items映射成一個個itemView,最后將List和Adapter設(shè)置給ListView。這樣當(dāng)我們改變List中的數(shù)據(jù),ListView就會相應(yīng)的刷新View。Flutter類似,我們準(zhǔn)備好Widgets(只不過Widget的“容器”是Tree而不是List),F(xiàn)lutter會提供Adapter(RenderObjectToWidgetAdapter)將其映射成渲染用的RenderObject,當(dāng)Widget更新時就會刷新界面。
另外,Widget也能通過設(shè)置Key來緩存復(fù)用,在類似ListView的場景中,Item Widget的復(fù)用是很有收益的。
2.基于共同祖先通信
在我們國家,如果你想和別人溝通上拉近距離,有時候會進入到類似“我們500年前是一家”的這種語境中。在Flutter中,如果兩個組件要通信,也是去找祖先(當(dāng)然,也有可能兩個組件本身就有遺傳關(guān)系),F(xiàn)lutter把它描述成“數(shù)據(jù)上行,通知下行”。
但是,在一個非常復(fù)雜的樹形層級中,要找到某位“祖先”并不是很容易的事情,而且性能也不好。Flutter為此做了優(yōu)化,提供了InheritedWidget,“祖先”Widget繼承該類型后,child可以通過BuildContext中提供的inheritFromWidgetOfExactType方法方便的找到在層級中離的最近的那位“祖先”。該方法做了優(yōu)化,效率很高,并且可以讓child和“祖先”建立依賴關(guān)系,方便做刷新。
Flutter中并沒有提倡類似controller的概念(像Android中的Activity,iOS中的ViewController),本身View是不可操作的,controller也就失去了意義。那么,組件之間的通信就必須在View層“自力更生”了。
3.函數(shù)式數(shù)據(jù)流
這肯定不是Flutter才有的,要想把響應(yīng)式實現(xiàn)的簡潔優(yōu)雅,就要利用好語言的函數(shù)式特性。Flutter的亮點是它使用的Dart語言能把這件事情變的很輕量,你基本不需要引入什么第三方庫就能做到(不過確實有RxDart庫,但感覺只是做了額外的增強),而且明顯語言Api的設(shè)計也往這個方向上做了優(yōu)化,非常方便。具體可以看看Stream和RxDart。
基于React的框架實踐
統(tǒng)一狀態(tài)管理和單向數(shù)據(jù)流
通過React的實踐,響應(yīng)式可以很好的解決數(shù)據(jù)到界面的更新,而且效率也不錯。但是自身對數(shù)據(jù)狀態(tài)的管理不足,React官方提出了Flux,而在面對復(fù)雜業(yè)務(wù)場景時,F(xiàn)lutter官方也是推薦Redux架構(gòu),我們也是根據(jù)這一思路搭建的框架。
首先是業(yè)務(wù)邏輯和界面分離,界面是無狀態(tài)(Stateless)的,我們也正在嘗試自動化的方法直接生成界面代碼,所以Widget中是不會有業(yè)務(wù)邏輯代碼的。當(dāng)我們把一個能描述當(dāng)前界面的數(shù)據(jù)(State)交給View層時,界面就應(yīng)該能正常展示。用戶和界面交互會產(chǎn)生Action,Action代表了用戶交互的意圖,Action可以攜帶信息(比如用戶使用輸入留言,Action中就應(yīng)該攜帶用戶留言的內(nèi)容信息)。Action會輸入給Store,Store會通過注冊的Interrupters對Action做前期攔截處理,可以通過Interrupter截攔Action,也可以把一個Action重新改寫成另外的Action。Store然后收集相應(yīng)綁定的Reducers對Action做一次reduce操作,產(chǎn)生新的State,并通知界面刷新。
通常我們在創(chuàng)建Store的時候就組冊好Reducer和Interrupter:
Store<PublishState> buildPublishStore(String itemId) {//設(shè)置狀態(tài)初始值
PublishState initState = new PublishState();
initState.itemId = itemId;
initState.isLoading = true;//創(chuàng)建Reducer和對應(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和對應(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中就是處理用戶交互時產(chǎn)生的Action的邏輯代碼,接收3個參數(shù),一個是執(zhí)行上下文,一個要處理的Action,一個是當(dāng)前的State,處理結(jié)束后必須返回新的State。函數(shù)式理想的Reducer應(yīng)該是一個無副作用的純函數(shù),顯然我們不應(yīng)該在Reducer中去訪問或者改變?nèi)钟虻淖兞?,但有時候我們會對前面的計算結(jié)果有依賴,這時可以將一些運行時數(shù)據(jù)寄存在ReduceContext中。Reducer中不應(yīng)該有異步邏輯,因為Store做Reduce操作是同步的,產(chǎn)生新State后會立即通知界面刷新,而異步產(chǎn)生對State的更新并不會觸發(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ò)請求就應(yīng)該放在Interrupter中實現(xiàn)。
*為什么會有Interrupter呢?換一個角度,我們可以把整個Store看成一個函數(shù),輸入是Action,輸出的是State。函數(shù)會有副作用,有時我們輸入?yún)?shù)并不一定得會相應(yīng)有輸出,比如日志函數(shù)( void log(String) ),我們輸入String只會在標(biāo)準(zhǔn)輸出上打印一個字符串,log函數(shù)不會有返回值。同樣,對Store來說,也不是所有的Action都要去改變State,用戶有時候觸發(fā)Action只要想讓手機震動下而已,并不會觸發(fā)界面更新。所以,Interrupter就是Store用來處理副作用的。
///截攔一個網(wǎng)絡(luò)請求的Action,并在執(zhí)行請求網(wǎng)絡(luò)后發(fā)出新Action
bool _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;
}
通常我們會讓一個界面根部的InheritedWidget來持有Store,這樣界面上的任何Widget都能方便的訪問到Store,并和Store建立聯(lián)系。這種做法可以參考redux_demo,再此不詳細展開。
最后簡單的說說Store的實現(xiàn),Store能夠接收Action,然后執(zhí)行reduce,最后向widget提供數(shù)據(jù)源。Widget可以基于提供的數(shù)據(jù)源建立數(shù)據(jù)流,響應(yīng)數(shù)據(jù)變更來刷新界面。這其中最核心的就是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中最核心的對Action進行reduce操作:
//收集該Action綁定的Reducer
final List<ReduceContext<State>> reducers = _reducers.values
.where((ctx) => ctx._handleWhats.any((what) => what == action.what))
.toList();
//執(zhí)行reduce
Box<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()
...
})
組件化的擴展
我們在業(yè)務(wù)開發(fā)中發(fā)現(xiàn),有時候一個頁面一個Store會帶來組件復(fù)用上的不方便,比如視頻播放組件是一個邏輯比較內(nèi)聚的組件,如果把它的reducer都集中放在頁面的Store中那么別的頁面想要復(fù)用這個開發(fā)好的視頻組件就不方便了,這時候視頻組件可能需要一個獨立的Store來存放視頻播放相關(guān)的邏輯。我們遵循Flutter組件通信方法,將框架擴展為允許存在多個Store,并且做到對Widget開發(fā)無感知。
Widget只能感知離它最近的Store持有者,該Store會向更高層級Store轉(zhuǎn)發(fā)Action,同時接收來自更高層級Store的數(shù)據(jù)變更并通知Widget。
到此,關(guān)于“Flutter React編程的方法是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。