Flutter狀態管理學習手冊(一)——ScopedModel

Windin發表於2019-04-02

一、ScopedModel簡介

ScopedModel屬於入門級別的狀態管理框架,它的思想比較簡單,參考官方文件便可以很容易理解其中構架。

FlutterLifting state up(狀態提升)是十分必要的,狀態提升可以理解為把元件之間相互共享的狀態提取出來放在一個較高層級中管理的一種思想。ScopedModel提供了對於這種狀態管理的便利。

二、ScopedModel中的三個概念

ScopedModel主要有三個重要的概念,也是其中的三個類:ModelScopedModelScopedModelDescendantScopedModel基本上通過這三個類實現其功能。

Model是封裝狀態和狀態操作的地方。我們可以將想要的資料存放在Model當中並且將對資料操作,如新增刪除的相關方法放在這裡。Model還提供了一個notifyListeners()方法,它的作用是當資料發生改變時,可以通過呼叫notifyListeners()方法通知介面進行更新。

ScopedModel是一個用於儲存ModelWidget。通常ScopedModel會一個應用的入口處作為父佈局使用,並以Model作為引數傳入,使得ScopedModel持有Model

ScopedModel的子佈局中,可以通過ScopedModel.of<Model>(context)方法來獲取Model

ScopedModelDescendant,顧名思義,是ScopedModel的派生物。同樣的,它也是一個WidgetScopedModelDescendant會作為ScopedModel下的子佈局存在,它的主要作用是響應狀態更新。

ScopedModelDescendant中存在builder函式,這個函式會在ModelnotifyListeners()發生時被呼叫,從而根據Model中的資料生成相應的介面。

三、ScopedModel的實踐

這裡以常見的獲取列表選擇列表為例子。一個頁面用於展示選中項和跳轉到列表,一個頁面用於顯示列表。

Flutter狀態管理學習手冊(一)——ScopedModel

1. 引入scoped_model第三方庫

在根目錄的pubspec.yaml檔案的dependencies中加入依賴

dependencies:
  ...
  scoped_model: ^1.0.0
複製程式碼

2. 定義Model

建立一個ListModel類,這個類需要繼承scoped_model包裡的Model類。

ListModel類中包含三個狀態:列表初始化標誌、列表資料、選中的列表項。

  bool _init = false; // 列表初始化標誌
  List<String> _list = []; // 列表資料
  String _selected = '未選中'; // 選中的列表項
複製程式碼

Model中不僅只有資料,還包括對資料操作的方法,這裡定義兩個操作方法,分別是選中列表專案和載入列表的方法,並且,這兩個方法在更新資料後,需要呼叫notifyListeners()通知UI更新。


  /**
   * 選中列表項
   */
  void select(String selected) {
    _selected = selected;

    // 通知資料變更
    notifyListeners();
  }

  /**
   * 載入列表
   */
  void loadList() async {
    // 模擬網路請求
    await Future.delayed(Duration(milliseconds: 3000));
    _list = [
      '1. Scoped Model',
      '2. Scoped Model',
      '3. Scoped Model',
      '4. Scoped Model',
      '5. Scoped Model',
      '6. Scoped Model',
      '7. Scoped Model',
      '8. Scoped Model',
      '9. Scoped Model',
      '10. Scoped Model'
    ];

    _init = true;
    // 通知資料變更
    notifyListeners();
  }
複製程式碼

3. UI佈局

在UI上,使用ScopedModel作為根佈局,提供Model,使用ScopedModelDescendant作為子佈局,響應Model

首先,在main()方法中,建立ListModel例項,用ScopedModel包裹MyApp佈局

void main() {

  // 建立Model例項
  ListModel listModel = ListModel();
  // 使用ScopedModel作為根佈局
  runApp(ScopedModel(model: listModel, child: MyApp()));
}
複製程式碼

為了體現狀態提升這一概念,例子中使用兩個頁面,一個是ShowPage,另一個是ListPageShowPage用於顯示選中的列表專案和提供跳轉到ListPage的入口,ListPage用於載入顯示列表。

ShowPage中,顯示ListModel中的選中項。

ScopedModelDescendant<ListModel>(
  builder: (context, child, model) {
    String selected = model.selected;
    return Text(selected);
  }
),
複製程式碼

ScopedModelDescendant的泛型指明ListModel,它便會自動獲取ScopedModel中的ListModel,在builder: (context, child, model)中即可通過其中的model引數獲取狀態,構建UI。

同樣的,在ListPage中,通過ScopedModelDescendant來顯示載入狀態和列表。

body: ScopedModelDescendant<ListModel>(builder: (context, child, model) {
        // 根據狀態顯示介面
        if (!model.isInit) {
          // 顯示loading介面
          return buildLoad();
        } else {
          // 顯示列表介面
          var list = model.list;
          return buildList(list);
        }
      }),
複製程式碼

4. 狀態改變

ListPage是一個StatefulWidget,所以可以在initState()方法中進行列表的載入工作。

@override
void initState() {
  super.initState();
  ListModel model = ScopedModel.of<ListModel>(context); // 獲取ListModel
  if (!model.isInit) {
    model.loadList(); // 載入列表
  }
}
複製程式碼

點選列表中的某一項時,會選中該項,這時呼叫ListModel中的select(String)方法,並返回上一個介面。

onTap: () {
  ListModel model = ScopedModel.of<ListModel>(context);
  model.select(list[index]);
  // 返回到上一級頁面
  Navigator.pop(context);
},
複製程式碼

完整程式碼可以參考github.com/windinwork/…

四、ScopedModel的注意事項

  1. ScopedModelDescendant的層級需要儘量低,可以避免大範圍的UI重建。這裡引用官方的例子。
// 錯誤示例
return ScopedModelDescendant<CartModel>(
  builder: (context, child, cart) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);
複製程式碼
// 正確示例
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: ScopedModelDescendant<CartModel>(
      builder: (context, child, cart) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);
複製程式碼

五、總結

ScopedModel功能比較簡單,使用Model儲存狀態和通知狀態改變,使用ScopedModel提供Model,使用ScopedModelDescendant佈局來響應狀態變化,是一個十分適合入門者理解的狀態管理模型。

參考目錄

Simple app state management

相關文章