Flutter | 狀態管理探索篇——Scoped Model(一)

前言

Flutter的不少靈感來自於React,它的設計思想是數據與視圖分離,由數據映射渲染視圖。因此在Flutter中,它的Widget是immutable的,而它的動態部分所有放到了狀態(State)中。react

假如你曾進行過react開發,也許你一下會想到Redux。flutter有相似redux的狀態管理的庫嗎?答案是確定的,可是有關在flutter中使用redux的應用實踐咱們會在以後的文章中進行介紹。git

這個系列將會從這幾個狀態管理方案進行深刻研究:github

  • Scoped_model
  • redux
  • BLoC
  • 對比總結篇

今天要和你們分享的是第一篇,使用Scoped_model進行狀態管理。redux

爲何須要狀態管理

在咱們一開始構建應用的時候,也許很簡單。咱們有一些狀態,直接把他們映射成視圖就能夠了。這種簡單應用可能並不須要狀態管理。架構

可是隨着功能的增長,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。app

Wow,這是什麼鬼。咱們很難再清楚的測試維護咱們的狀態,由於它看上去實在是太複雜了!並且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展現的時候,外部也須要顯示點贊數,這時候就須要同步這兩個狀態。

這時候,咱們便迫切的須要一個架構來幫助咱們理清這些關係,狀態管理框架應運而生。框架

什麼是Scoped_model

Scoped_model是一個dart第三方庫,提供了讓您可以輕鬆地將數據模型從父Widget傳遞到它的後代的功能。此外,它還會在模型更新時從新渲染使用該模型的全部子項。less

它直接來自於Google正在開發的新系統Fuchsia核心Widgets 中對Model類的簡單提取,做爲獨立使用的獨立Flutter插件發佈。ide

實現原理

Scoped model使用了觀察者模式,將數據模型放在父代,後代經過找到父代的model進行數據渲染,最後數據改變時將數據傳回,父代再通知全部用到了該model的子代去更新狀態。post

而咱們則須要將它們放在頂層入口MaterialApp之上,這樣就能進行全局的狀態管理了。

這裏page3,page4表明使用到該狀態(model)的子頁面。

Lets do it!

這裏咱們以一個最簡單的CountApp舉例,詳細介紹Scoped_model的用法。該項目完整代碼已放在github倉庫

這是一個在不一樣頁面使用Scoped共享狀態信息的app。這兩個頁面都依賴於一個數字,這個數字會隨着咱們按下按鈕的次數而增長。

第一步:添加依賴

在pubspec中添加scoped_model的依賴。

第二步:建立Model

在Scoped中,Model是一個只包含與狀態相關信息的單位。咱們應該把狀態數據與操做數據的方法抽象出來封裝到Model中。

import 'package:scoped_model/scoped_model.dart';

class CountModel extends Model{
  int _count = 0;
  get count => _count;
  
  void increment(){
    _count++;
    notifyListeners();
  }
}
複製代碼
  • 咱們須要讓咱們自定義的CountModel繼承至Model。
  • 在狀態發生變化時(increment)通知全部用到了該model的子項更新狀態。(notifyListeners)

第三步:將Model放入頂層

//建立頂層狀態
  CountModel countModel = CountModel();

  @override
  Widget build(BuildContext context) {
    return ScopedModel<CountModel>(
      model: countModel,
      child: new MaterialApp(
        home: TopScreen(),
      ),
    );
  }
複製代碼
  • 咱們在頂層建立了一個CountModel的實例。
  • ScopedModel<T extends Model>是一個StatelessWidget,它接收一個model,並提供給須要它的全部部件。
  • 將ScopedModel<T extends Model>的model屬性綁定咱們的CountModel對象。

第四步:在子頁面中獲取Model

咱們能夠從前面的演示圖片中看出,一共有兩個頁面,都使用了同一個model。 Scoped_model提供了兩種方式在子頁面中獲取model。咱們先來介紹第一種,使用ScopedModelDescendant獲取model。

@override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model){
        return Scaffold(
          body: Center(
            child: Text(
              model.count.toString(),
              style: TextStyle(fontSize: 48.0),
            ),
          ),
        );
      },
    );
  }
複製代碼
  • ScopedModelDescendant<T extends Model>是一個Stateless Widget,它接收三個參數。
  • builder是一個ScopedModelDescendantBuilder,它接收三個參數。
    ,在builder中可以經過model來獲取CountModel實例。
  • rebuildOnChange屬性可以控制當該狀態發生變化時,是否rebuild,做用等同於setState。也就是說咱們調用改變狀態的一些方法時,沒必要再setState。
floatingActionButton: new FloatingActionButton(
          onPressed: () => model.increment(),
          tooltip: 'Increment',
          child: new Icon(Icons.add),
        )
複製代碼

第二種獲取model的方式——使用ScopedModel.of

final countModel = ScopedModel.of<CountModel>(context);
countModel.increment();
複製代碼

或者在Model中重寫of方法

class CountModel extends Model{
  int _count = 0;
  get count => _count;

  void increment(){
    _count++;
    notifyListeners();
  }
//重寫of方法
  CountModel of(context) =>
      ScopedModel.of<CountModel>(context);
}
複製代碼

而後直接經過CountModel獲取model實例

final countModel2 = CountModel().of(context);
複製代碼

這種方式彷佛讓咱們的代碼有更好的可閱讀性。

【注意:】咱們在使用第二種方式的時候,rebuildOnChange屬性默認爲false,因此會致使沒法刷新(同步)狀態的狀況發生,須要手動指定rebuildOnChange:true。這裏要很是感謝@榮毅coolboy同窗的分享!

Q&A

這裏看上去彷佛只添加了一個model,我應該如何添加多個model

要解決這個問題很簡單,使用Mixin!

class MainModel extends Model with AModel,BModel,CModel{}
複製代碼

而後將MainModel放在頂層便可。 這裏有一個比較完整的使用ScopedModel管理狀態的應用,詳細用法可參考該項目。

Scoped是如何作到同步不一樣頁面中的狀態的

Model實現了Listenable接口,並重寫了void addListener(VoidCallback listener),removeListener(VoidCallback listener)方法,實現了觀察者模式。 每當咱們調用notifyListeners()方法時,將會通知觀察者更新狀態。

Scoped如何作到數據可以互相共享的

在不一樣頁面間的數據傳遞使用了InheritedWidget。

侵入性

因爲Model必須繼承至Model類,因此它就具備了侵入性。之後假如不用scoped進行狀態管理那麼必然會帶來須要更改多處代碼的狀況。這並非咱們但願看到的結果。

寫在最後

在flutter中,Scoped_model是一種很是簡單易上手,並能保持代碼高可閱讀性的一種新的狀態管理方式,值得各位去嘗試一下!

本次所用到的代碼已經上傳Github: github.com/Vadaski/Vad…

若是您對scoped還有任何疑問或者文章的建議,歡迎在下方評論區以及個人郵箱1652219550a@gmail.com與我聯繫,我會及時回覆!

下一章咱們將探索Redux在Flutter中的實踐,敬請關注。

相關文章
相關標籤/搜索