Flutter | 狀態管理探索篇——Scoped Model(一)
Flutter的很多靈感來自於React,它的設計思想是資料與檢視分離,由資料 對映 渲染檢視。所以在Flutter中,它的Widget是immutable的,而它的動態部分全部放到了狀態(State)中。
假如你曾進行過react開發,也許你一下會想到 Redux 。flutter有類似redux的狀態管理的庫嗎?答案是肯定的,但是有關在 flutter中使用redux 的應用實踐我們會在之後的文章中進行介紹。
今天要和大家分享的是使用Scoped_model進行狀態管理。
什麼是Scoped_model
Scoped_model是一個dart第三方庫,提供了讓您能夠輕鬆地將 資料模型 從父Widget傳遞到它的後代的功能。此外,它還會在模型 更新時重新渲染 使用該模型的所有子項。
它直接來自於Google正在開發的新系統Fuchsia核心Widgets 中對Model類的簡單提取,作為獨立使用的獨立Flutter外掛釋出。
實現原理
Scoped model使用了觀察者模式,將資料模型放在父代,後代通過找到父代的model進行資料渲染,最後資料改變時將資料傳回,父代再 通知所有 用到了該model的子代去更新狀態。
而我們則需要將它們放在頂層入口MaterialApp之上,這樣就能進行全域性的狀態管理了。

這裡page3,page4代表使用到該狀態(model)的子頁面。
Lets do it!
這裡我們以一個最簡單的CountApp舉例,詳細介紹Scoped_model的用法。該專案完整程式碼已放在 ofollow,noindex">github倉庫 。
這是一個在不同頁面使用Scoped共享狀態資訊的app。這兩個頁面都依賴於一個數字,這個數字會隨著我們按下按鈕的次數而增加。

第一步:新增依賴
在pubspec中新增scoped_model的依賴。

- 實際新增請參考: pub.dartlang.org/packages/sc…
- 由於版本衝突新增失敗請參考: juejin.im/post/5b8958…
第二步:建立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
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,它接收三個引數。
- 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); 複製程式碼
這種方式似乎讓我們的程式碼有更好的可閱讀性。
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。

是否具有侵入性
正在研究當中...
寫在最後
在flutter中,Scoped_model是一種非常簡單易上手,並能保持程式碼高可閱讀性的一種新的狀態管理方式,值得各位去嘗試一下!
本次所用到的程式碼已經上傳Github: github.com/Vadaski/Vad…
如果您對scoped還有任何疑問或者文章的建議,歡迎在下方評論區以及我的郵箱[email protected]與我聯絡,我會及時回覆!
下一章我們將探索Redux在Flutter中的實踐,敬請關注。