⚠️Flutter的 狀態管理⚠️

iOShuyang發表於2020-06-01

?歡迎前往本人的GitHub檢視更多內容。點選前往GitHub

1 對於狀態管理的理解

最近專案開發完成後,沒有什麼任務安排,就自己對專案進行了查缺補漏。如果說flutter開發中最不可避免的話,肯定是狀態管理啦。我們自己的專案當中用的是Provider來進行管理的,自己其實對於狀態管理的基礎還是有一點模糊,粗淺的理解就是:避免對頁面不停的build,要進行鍼對性的重新整理,(列入:更新一個text也將整個頁面build一次浪費效能。)



1.0 簡單理解

那我們來言歸正傳說一說比較正確的理解吧,避免我在這裡誤人子弟。

1.狀態管理的目的就是為了讓介面與業務分離。
2.當我們的應用功能複雜多樣的時候,應用程式將會有幾十個甚至上百個狀態,這時候我們需要對狀態進行合理有效的管理。
複製程式碼

很多從指令式程式設計框架(Android或iOS原生開發者)轉成宣告式程式設計(Flutter、Vue、React等)剛開始並不適應,因為需要一個新的角度來考慮APP的開發模式。Flutter作為一個現代的框架,是宣告式程式設計的:

⚠️Flutter的 狀態管理⚠️
在編寫一個應用的過程中,我們有大量的狀態需要來進行管理,而正是對這些State的改變,來更新介面的重新整理:
⚠️Flutter的 狀態管理⚠️

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是一種單向資料流架構,可以輕鬆開發,維護和測試應用程式。
複製程式碼

⚠️Flutter的 狀態管理⚠️

我們在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方式構建應用的方法,這是一個由流構成的完全非同步的世界。
複製程式碼

⚠️Flutter的 狀態管理⚠️

* 用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方法。
複製程式碼






複製程式碼

參考來源:

State management

Flutter移動應用:狀態管理

Flutter | 狀態管理指南篇——Provider

八種 Flutter 狀態管理-深入評論

相關文章