?歡迎前往本人的GitHub檢視更多內容。點選前往GitHub
1 對於狀態管理的理解
最近專案開發完成後,沒有什麼任務安排,就自己對專案進行了查缺補漏。如果說flutter開發中最不可避免的話,肯定是狀態管理啦。我們自己的專案當中用的是Provider來進行管理的,自己其實對於狀態管理的基礎還是有一點模糊,粗淺的理解就是:避免對頁面不停的build,要進行鍼對性的重新整理,(列入:更新一個text也將整個頁面build一次浪費效能。)
1.0 簡單理解
那我們來言歸正傳說一說比較正確的理解吧,避免我在這裡誤人子弟。
1.狀態管理的目的就是為了讓介面與業務分離。
2.當我們的應用功能複雜多樣的時候,應用程式將會有幾十個甚至上百個狀態,這時候我們需要對狀態進行合理有效的管理。
複製程式碼
很多從指令式程式設計框架(Android或iOS原生開發者)轉成宣告式程式設計(Flutter、Vue、React等)剛開始並不適應,因為需要一個新的角度來考慮APP的開發模式。Flutter作為一個現代的框架,是宣告式程式設計的:
在編寫一個應用的過程中,我們有大量的狀態需要來進行管理,而正是對這些State的改變,來更新介面的重新整理:
1.1 InheritedWidget(共享應用程式狀態)
Flutter 中Widget 多種多樣,有UI的,當然也有功能型的元件InheritedWidget 元件就是Flutter 中的一個功能元件,它可以實現Flutter 元件之間的資料共享,他的資料傳遞方向在Widget樹傳遞是從上到下的。
使用樣列:
複製程式碼
class StateManagementDemo extends StatefulWidget {
@override
_StateManagementDemoState createState() => _StateManagementDemoState();
}
class _StateManagementDemoState extends State<StateManagementDemo> {
int _count = 0;
void _increaseCount () {
setState(() {
_count += 1;
});
}
@override
Widget build(BuildContext context) {
return CounterProvider(
count: _count,
increaseCount: _increaseCount,
child: Scaffold(
appBar: AppBar(title: Text('StateManagementDemo'), elevation: 0.0,),
body: CounterWrapper(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _increaseCount,
),
),
);
}
}
class CounterWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Counter(),);
}
}
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
final int count = CounterProvider.of(context).count;
final VoidCallback increaseCount = CounterProvider.of(context).increaseCount;
return Center(child: ActionChip(
label: Text('$count'),
onPressed: increaseCount,),
);
}
}
class CounterProvider extends InheritedWidget {
final int count;
final VoidCallback increaseCount;
final Widget child;
CounterProvider({
this.count,
this.increaseCount,
this.child,
}) : super(child: child);
// 其他部件中可以用這個方法得到小部件內的資料
static CounterProvider of(BuildContext context) => context.inheritFromWidgetOfExactType(CounterProvider);
// 決定是否通知繼承這個小部件的小部件,當這個部件重建以後有時候需要通知繼承這個部件的小部件
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
複製程式碼
2 狀態管理框架
2.0 可以通過如下方式來進行狀態管理
1.setState :
最重要的方式 setState,支援規模較小的程式足夠了,所有其它方式最終都需要呼叫 setState。
2.Function callback
Dart Function 足夠靈活,還支援模版引數。
typedef FooChanged = void Function(int);
typedef ValueChanged<T> = void Function(T value);
單向變更通知,可以和ObserverList結合支援多個訂閱者。
Flutter 內建 ChangeNotifier, ValueNotifier 都可以認為是類似方案。
3.Delegate
可以認為是多個回撥函式,其他語言裡都有類似模式,名稱似乎來源於 Objective-C。實際例子
abstract class SpiderDelegate {
/// category is null, crawl book whole site
/// category not null, crawl book under the category
void onBook(Book book, {Site site, Category category});
void onChapter(Book book, Chapter chapter);
}
4.Sigslot
源自 Qt 裡的經典程式設計模式,Dart 可以輕易實現。這種方式在 Flutter 裡可能根本不會有太多應用,但是由於 Sigslot 在 C++ 領域具有舉足輕重的地位,屬於介面資料和邏輯解耦合的王者,boost::signal(2)就是明證,在這裡列出純屬湊數(本人也實現了一個)。
typedef ValueCallback<E> = void Function(E value);
abstract class Signable<E> {
// Signable<bool> someValue;
/// Register a closure to be called when the object notifies its listeners.
void connect(ValueCallback<E> listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies.
void disconnect(ValueCallback<E> listener);
/// sink value changed
void emit(E value);
}
使用方法類似
Signal<String> signalString;
signalString.connect((String str) {
// got the `str` changed here
});
複製程式碼
2.1 scoped_model
Scoped_model是一個dart第三方庫,提供了讓您能夠輕鬆地將資料模型從父Widget傳遞到它的後代的功能。此外,它還會在模型更新時重新渲染使用該模型的所有子項。
它直接來自於Google正在開發的新系統Fuchsia核心Widgets 中對Model類的簡單提取,作為獨立使用的獨立Flutter外掛釋出。
Scoped model使用了觀察者模式,將資料模型放在父代,後代通過找到父代的model進行資料渲染,最後資料改變時將資料傳回,父代再通知所有用到了該model的子代去更新狀態。
複製程式碼
2.2 Redux
Redux是一種單向資料流架構,可以輕鬆開發,維護和測試應用程式。
複製程式碼
我們在Redux中,所有的狀態都儲存在Store裡。這個Store會放在App頂層。
View拿到Store儲存的狀態(State)並把它對映成檢視。View還會與使用者進行互動,使用者點選按鈕滑動螢幕等等,這時會因為互動需要資料發生改變。
Redux讓我們不能讓View直接運算元據,而是通過發起一個action來告訴Reducer,狀態得改變啦。
這時候Reducer接收到了這個action,他就回去遍歷action表,然後找到那個匹配的action,根據action生成新的狀態並把新的狀態放到Store中。
Store丟棄了老的狀態物件,儲存了新的狀態物件後,就通知所有使用到了這個狀態的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態了。
複製程式碼
2.3 BLoC
BLoC是一種利用reactive programming方式構建應用的方法,這是一個由流構成的完全非同步的世界。
複製程式碼
* 用StreamBuilder包裹有狀態的部件,streambuilder將會監聽一個流
* 這個流來自於BLoC
* 有狀態小部件中的資料來自於監聽的流。
* 使用者互動手勢被檢測到,產生了事件。例如按了一下按鈕。
* 呼叫bloc的功能來處理這個事件
* 在bloc中處理完畢後將會吧最新的資料add進流的sink中
* StreamBuilder監聽到新的資料,產生一個新的snapshot,並重新呼叫build方法
* Widget被重新構建
複製程式碼
2.4 RxDart
RxDart是基於ReactiveX標準API的Dart版本實現,由Dart標準庫中Stream擴充套件而成。因此,RxDart與Dart的相關術語稍有區別:
Dart RxDart
StreamController Subject
Stream Observable
Observable等同於Stream,Subject等同於StreamController,前者均由後者繼承而來。
不同於Dart,RxDart提供了三種StreamController的變體來應用到不同的場景:
PublishSubject
BehaviorSubject
ReplaySubject
複製程式碼
2.5 Fish-Redux【推薦?】
Fish Redux 是一個基於 Redux 資料管理的組裝式 flutter 應用框架, 它特別適用於構建中大型的複雜應用。它的特點是配置式組裝。
一方面我們將一個大的頁面,對檢視和資料層層拆解為互相獨立的 Component|Adapter,上層負責組裝,下層負責實現;
另一方面將 Component|Adapter 拆分為 View,Reducer,Effect 等相互獨立的上下文無關函式。
所以它會非常乾淨,易維護,易協作。
Fish Redux 的靈感主要來自於 Redux, Elm, Dva 這樣的優秀框架。而 Fish Redux 站在巨人的肩膀上,將集中,分治,複用,隔離做的更進一步。
複製程式碼
2.7 Provide
和Scoped_model一樣,Provide也是藉助了InheritWidget,將共享狀態放到頂層MaterialApp之上。底層部件通過Provier獲取該狀態,並通過混合ChangeNotifier通知依賴於該狀態的元件重新整理。
Provide還提供了Provide.stream,讓我們能夠以處理流的方式處理資料,不過目前還有一些問題,不推薦使用。
複製程式碼
2.6 Provider 【推薦???】
2019 Google I/O 大會上重磅訊息出了支援 flutter_web 之外,另一個便是棄用之前的狀態管理 Provide,轉而推薦相似的庫 Provider;雖然只有一個字母之差使用方式差別卻很大;小菜初步學習一下新的狀態管理庫 Provider;
Flutter 針對不同型別物件提供了多種不同的 Provider;Provider 也是藉助了 InheritWidget,將共享狀態放到頂層 MaterialApp 之上;
複製程式碼
3 實際開發中遇到的坑
3.1 ⚠️Provider中如何抉擇 Consumer 還是 Selector
當我們需要更新頁面的時候,我們會用notifyListeners();
但是如果我們用Consumer來包裹一個listview我們列印會發現已經build過的item都會重新的build一次。
其實這樣有一點不合理,因為我們只需要更新的是其中一個item。
這時候如果我們用Selector來處理的話就會發現大有不同。它只會build我們需要更新的那個item這樣的話就更加合理。
但是使用Selector的話,需要注意的是shouldRebuild用什麼來決定是否重新build
複製程式碼
3.2 ⚠️Provider使用Provider.of(context) 獲取頂層資料的坑
如果我們APage使用Provider.of(context) 獲取頂層資料,然後BPage對資料進行了更改,其實會影響到APage的然後會重新執行其 build。
其實我們command點選of進到底層程式碼發現,除了context以外還有一個引數listen。
如果我們在APage設定listen:false,這樣就不會因為BPage的操作影響到APage了
複製程式碼
3.3 ⚠️SingleTickerProviderStateMixin與TickerProviderStateMixin的坑
當使用vsync: this的時候,State物件必須with SingleTickerProviderStateMixin或TickerProviderStateMixin
首先我們要搞清楚SingleTickerProviderStateMixin於TickerProviderStateMixin的區別:
TickerProviderStateMixin適用於多AnimationController的情況
SingleTickerProviderStateMixin適用於單個AnimationController的情況
其實非必須用TickerProviderStateMixin,建議是用SingleTickerProviderStateMixin的。
1.因為如果 APage 使用 with TickerProviderStateMixin,當從APage 跳轉到BPage的時候或從BPage返回到APage的時候你列印會發現都呼叫了build方法。
2.如果不使用 with TickerProviderStateMixin,當從APage 跳轉到BPage的時候或從BPage返回到APage的時候都不會呼叫了build方法。
複製程式碼
複製程式碼
參考來源: