下面開始實際分析 Provider 。
二、Provider
為什麼會有 Provider ?
因為 Flutter 與 React 技術棧的相似性,所以在 Flutter 中湧現了諸如flutter_redux
、flutter_dva
、 flutter_mobx
、 fish_flutter
等前端式的狀態管理,它們大多比較複雜,而且需要對框架概念有一定理解。
而作為 Flutter 官方推薦的狀態管理 scoped_model
,又因為其設計較為簡單,有些時候不適用於複雜的場景。
所以在經歷了一端坎坷之後,今年 Google I/O 大會之後, Provider 成了 Flutter 官方新推薦的狀態管理方式之一。
它的特點就是: 不複雜,好理解,代碼量不大的情況下,可以方便組合和控制刷新顆粒度 , 而原 Google 官方倉庫的狀態管理 flutter-provide 已宣告GG , provider 成了它的替代品。
``! ??注意,
provider比
flutter-provide多了個
r`。
> 題外話:以前面試時,偶爾會被面試官問到「你的開源項目代碼量也不多啊」這樣的問題,每次我都會笑而不語,**雖然代碼量能代表一些成果,但是我是十分反對用代碼量來衡量貢獻價值,這和你用加班時長來衡量員工價值有什麼區別?**
### 0、演示代碼
如下代碼所示, 實現的是一個點擊計數器,其中:
- `_ProviderPageState` 中使用`MultiProvider` 提供了多個 `providers` 的支持。
- 在 `CountWidget` 中通過 `Consumer` 獲取的 `counter ` ,同時更新 `_ProviderPageState` 中的 `AppBar` 和 `CountWidget ` 中的 `Text ` 顯示。
class ProviderPageState extends State { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(builder: ( ) => ProviderModel()), ], child: Scaffold( appBar: AppBar( title: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { var counter = Provider.of(context); return new Text("Provider ${counter.count.toString()}"); }, ) ), body: CountWidget(), ), ); } }
class CountWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer(builder: (context, counter, _) { return new Column( children: [ new Expanded(child: new Center(child: new Text(counter.count.toString()))), new Center( child: new FlatButton( onPressed: () { counter.add(); }, color: Colors.blue, child: new Text("+")), ) ], ); }); } }
class ProviderModel extends ChangeNotifier { int _count = 0;
int get count => _count;
void add() { _count++; notifyListeners(); } }
所以上述代碼中,我們通過 `ChangeNotifierProvider ` 組合了 `ChangeNotifier` (ProviderModel) 實現共享;利用了 ` Provider.of` 和 `Consumer ` 獲取共享的 `counter` 狀態;通過調用 `ChangeNotifier` 的 ` notifyListeners();` 觸發更新。
這裡幾個知識點是:
- 1、 **Provider** 的內部 `DelegateWidget` 是一個 `StatefulWidget` ,所以可以更新且具有生命周期。
- 2、狀態共享是使用了 `InheritedProvider` 這個 `InheritedWidget` 實現的。
- 3、巧妙利用 `MultiProvider` 和 `Consumer` 封裝,實現了組合與刷新顆粒度控制。
接著我們逐個分析
### 1、Delegate
既然是狀態管理,那麼肯定有 `StatefulWidget` 和 `setState` 調用。
在 **Provider** 中,一系列關於 `StatefulWidget` 的生命周期管理和更新,都是通過各種代理完成的,如下圖所示,上面代碼中我們用到的 `ChangeNotifierProvider ` 大致經歷了這樣的流程:
- 設置到 `ChangeNotifierProvider ` 的 `ChangeNotifer` 會被執行 `addListener` 添加監聽 `listener`。
- `listener` 內會調用 `StateDelegate` 的 `StateSetter` 方法,從而調用到 `StatefulWidget` 的 `setState`。
- 當我們執行 `ChangeNotifer` 的 `notifyListeners ` 時,就會最終觸發 `setState` 更新。
![](http://img.cdn.guoshuyu.cn/20190616_Flutter-15/image6)
而我們使用過的 `MultiProvider` 則是允許我們組合多種 `Provider` ,如下代碼所示,傳入的 `providers` 會倒序排列,最後組合成一個嵌套的 Widget tree ,方便我們添加多種 `Provider` :
@override Widget build(BuildContext context) { var tree = child; for (final provider in providers.reversed) { tree = provider.cloneWithChild(tree); } return tree; }
/// Clones the current provider with a new [child]. /// Note for implementers: all other values, including [Key] must be /// preserved. @override MultiProvider cloneWithChild(Widget child) { return MultiProvider( key: key, providers: providers, child: child, ); }
通過 `Delegate` 中回調出來的各種生命周期,如 `Disposer `,也有利於我們外部二次處理,減少外部 `StatefulWidget ` 的嵌套使用。
### 2、InheritedProvider
狀態共享肯定需要 `InheritedWidget` ,`InheritedProvider ` 就是`InheritedWidget ` 的子類,所有的 `Provider` 實現都在 `build` 方法中使用 `InheritedProvider ` 進行嵌套,實現 `value` 的共享。
### 3、Consumer
`Consumer ` 是 `Provider` 中比較有意思的東西,它本身是一個 `StatelessWidget` , 只是在 `build ` 中通過 ` Provider.of<T>(context)` 幫你獲取到 `InheritedWidget` 共享的 `value` 。
final Widget Function(BuildContext context, T value, Widget child) builder;
@override Widget build(BuildContext context) { return builder( context, Provider.of(context), child, ); }
那我們直接使用 `Provider.of<T>(context)` ,不使用 `Consumer ` 可以嗎?
當然可以,但是你還記得前面,我們在介紹 `InheritedWidget` 時所說的:
> 傳入的 `context` 代表著這個 `Widget` 的 `Element` 在 `InheritedElement` 里被「登記」到 `_dependents` 了。
`Consumer ` 做為一個單獨 `StatelessWidget` ,**它的好處就是 `Provider.of<T>(context)` 傳入的 `context` 就是 `Consumer ` 它自己。** 這樣的話,我們在需要使用 `Provider.value` 的地方用 `Consumer` 做嵌套, `InheritedWidget` 更新的時候,就不會更新到整個頁面 , 而是僅更新到 `Consumer ` 這個 `StatelessWidget` 。
**所以 `Consumer ` 貼心的封裝了 `context` 在 `InheritedWidget` 中的「登記邏輯」,從而控制了狀態改變時,需要更新的精細度。**
同時庫內還提供了 `Consumer2` ~ `Consumer6` 的組合,感受下 :
@override Widget build(BuildContext context) { return builder( context, Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), Provider.of(context), ch ild, );
這樣的設定,相信用過 BLoC 模式的同學會感覺很貼心,以前正常用做 BLoC 時,每個 `StreamBuilder` 的 `snapShot` 只支持一種類型,多個時*要不就是多個狀態合併到一個實體,要不就需要多個StreamBuilder嵌套。*
當然,如果你想直接利用 `LayoutBuilder` 搭配 `Provider.of<T>(context)` 也是可以的:
LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { var counter = Provider.of(context); return new Text("Provider ${counter.count.toString()}"); } ```
f="">其他的還有 ValueListenableProvider 、Fut
ureProvider 、Str
eamProvider 等
多種 Provider ,可見整個 Provider 的設計上更貼近 Flutter 的原生特性,同時設計也更好理解,並且兼顧了性能等問題。
Provider 的使用指南上,更詳細的 Vadaski 的 《Flutter | 狀態管理指南篇——Provider》 已經寫過,我就不重複寫輪子了,感興趣的可以過去看看。
自此,第十五篇終於結束了!(///▽///)
資源推薦
Github : https:// github.com/CarGuo
本文Demo :https:// github.com/CarGuo/state _manager_demo
完整項目 :https:// github.com/CarGuo/GSYGi thubAppFlutter
完整開源項目推薦:
GSY Flutter 實戰系列電子書
GSYGithubApp Flutter
GSYGithubApp React Native
GSYGithubAppWeex
推薦閱讀: