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