provider的使用以及優化心得

雨三樓發表於2021-03-29

provider基於觀察者(釋出-訂閱)模式。

provider 的組成以及作用:

Listenable

作為釋出者的角色,需要和Provider配合使用,相關元件有:

  • Listenable 抽象類,需要自己繼承,實現addListener removeListener 函式 以及 維護listeners列表。
  • ChangeNotifier 繼承於Listenable 內部提供實現好的釋出者功能,常於ChangeNotifierProvider相結合使用。
  • ValueNotifier 繼承於ChangeNotifier,和ValueListenableProvider配合使用, 區別在於,內部提供了單一變數的狀態變化自動通知,不需要自行呼叫notifyListeners()。缺點在於犧牲了靈活性,僅對於內部value的set方法做了監聽。

Consumer ConsumerX

作為訂閱者的角色,也是一個便捷構造器,內部幫我們呼叫Provider.of(context)來獲取ChangeNotifier,以及將將自身加入訂閱者中。

也可以不使用Consumer,直接在widget中使用Provder.of(conext) 是一樣的效果。

Provider

Provider 基於一個DelegateWidget來實現,它提供一對creat和dispose,來讓我們管理資料的生命週期,所以通過Provider我們不用擔心記憶體釋放以及資料請求等問題(這種場景下完全可以替代StatefulWidget)。

除此之外,Provider還提供了不同的子類來適應各種場景:

  • Provider :不僅提供creat和dispose,還擁有Inherited特性,它繼承於InheritedProvider,其內部基於InheritedWidget實現。但是Provider並不具備重新整理子部件的功能,僅能用於提供固定資料。
  • ListenableProvider :一個偏底層且擁有重新整理子部件功能的Provider,需要提供一個繼承於Listenable的釋出者進行訊息釋出。
  • ChangeNotifierProvider : 繼承於ListenableProvider,需要和ChangeNotifier結合使用:
@override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CustomChangeNotifier>(
      create: (BuildContext context) {
        return CustomChangeNotifier();
      },
      child: Consumer<CustomChangeNotifier>(
        builder: (builder: (context, notifier, _) {
      return new Column(
        children: <Widget>[
          new Expanded(child: new Center(child: new Text(notifier.count.toString()))),
          new Center(
            child: new FlatButton(
                onPressed: () {
                  notifier.add();
                },
                color: Colors.blue,
                child: new Text("+")),
          )
        ],
      );
  }
複製程式碼
  • FutureProvider:支援非同步,需要提供一個Future,等待Future完成後,自動重新整理子部件。
  • StreamProvider:支援非同步,需要提供一個Steam充當釋出者,當有流輸入後,重新整理子部件。
  • ValueListenableProvider : 支援單一變數,需要提供一個ValueNotifier對資料進行包裝,
  • MultiProvider 針對Provider進行組合:
void main() {
  final counter = CounterModel();
  final textSize = 48;

  runApp(
    MultiProvider(
      providers: [
        Provider.value(value: textSize),
        ChangeNotifierProvider.value(value: counter)
      ],
      child: MyApp(),
    ),
  );
}
複製程式碼
  • ProxyProvider ProxyProviderX :結合MultiProvider可以相應其他Provider的更新:

比如一個圖片上傳,我們可以通過一個ChangeNotifierProvider進行圖片上傳獲取URL,然後通過ProxyProvider攔截上傳後的URL,把上傳到伺服器中。

/// 官方示例
Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => Counter()),
      ProxyProvider<Counter, Translations>(
        update: (_, counter, __) => Translations(counter.value),
      ),
    ],
    child: Foo(),
  );
}

class Translations {
  const Translations(this._value);

  final int _value;

  String get title => 'You clicked $_value times';
}
複製程式碼

Selector

在業務層的細粒度重新整理下,光靠Provider控制是遠遠不夠的,這時候,就需要Selector。 Selector和Consumer扮演一樣的角色—訂閱者,使用起來也和Consummer差不多,只不過多一個selector。

實現原理:

Selector繼承於SingleChildStatefulWidget,為了防止重複構建,重寫了buildWithChild並對widget進行了快取,通過selector選擇器傳回來的資料進行equals判斷,當然還有_shouldRebuild可以進行強制構建。

如何使用:

Selector接受兩個泛型變數 Selector<T, S>,通過Provider.of(context),獲取釋出者,同時S會作為檢視是否需要重新構建的資料型別。

 Selector<Translations, String>(
      selector: (context, provider) {
        return provider.title; // 將改變後的資料返回
      },
      builder: (context, value, child) {
        print("重新整理了");
        return Text("$value"); // 重新構建
      },
),
複製程式碼

優化心得:

區域性重新整理控制

Provider具有非常好的生命週期控制,控制好重新整理範圍,儘量支援最小化原則,這樣不僅對於重新整理速度有所提升,在資料控制方面例如記憶體的申請和釋放頻次,也有更好的優化。

善用懶載入

這裡的懶載入是指的是create的延遲呼叫,通常create中還會進行網路請求、資料初始化等操作,且在provider的整個生命週期中,只呼叫一次,如果想要關閉懶載入,需要將lazy置為false。

Dispose釋放

provider對於記憶體控制很看重,除了各類Provider的dispose支援,ChangeNotifier也提供了dispose,

相關文章