進入這個帖子,能夠先看看前面的介紹哈。git
selector.dart中有段關於Selector的介紹github
/// {@template provider.selector} .... /// {@endtemplate} class Selector<A, S> extends Selector0<S> { } class Selector0<T> extends SingleChildStatefulWidget { final ValueWidgetBuilder<T> builder; final T Function(BuildContext) selector; final ShouldRebuild<T> _shouldRebuild; } 複製代碼
咱們大體領會到一下關鍵點。緩存
immutable:一個類的對象在經過構造方法建立後若是狀態不會再被改變,那麼它就是一個不可變(immutable)類。它的全部成員變量的賦值僅在構造方法中完成,不會提供任何 setter 方法供外部類去修改。markdown
//tuple舉例 Selector<Foo, Tuple2<Bar, Baz>>( selector: (_, foo) => Tuple2(foo.bar, foo.baz), builder: (_, data, __) { return Text('${data.item1} ${data.item2}'); } ) 複製代碼
接下來,是一個Selector的使用場景。 Demo倉庫地址
入口:main_provider.dart -- Selector使用app
頁面上有三個字母按鈕,統計每一個按鈕點擊次數。點擊按鈕,則按鈕上數字+1。 less
//首先咱們有一個model,作數據處理,ChangeNotifier用到Provider中 class CountModel extends ChangeNotifier { //key爲字母,value爲點擊次數統計 Map<String, int> contentMap = SplayTreeMap(); //初始化數據 initData() { contentMap["a"] = 0;contentMap["b"] = 0;contentMap["c"] = 0; } //增長字母按鈕的點擊次數 increment(String content) { contentMap[content] = contentMap[content] + 1; //通知刷新 notifyListeners(); } } class SelectorDemoWidget extends StatefulWidget { SelectorDemoWidget({Key key}) : super(key: key); @override _SelectorDemoWidgetState createState() => _SelectorDemoWidgetState(); } class _SelectorDemoWidgetState extends State<SelectorDemoWidget> { CountModel _model; @override void initState() { super.initState(); //初始化數據 _model = new CountModel()..initData(); } @override Widget build(BuildContext context) { //構建一組字母按鈕(CountItemWidget) List<CountItemWidget> _children = _model.contentMap.keys .map((key) => CountItemWidget(content: key)) .toList(); return Scaffold( appBar: AppBar(title: Text("Selector示例"),), //ChangeNotifierProvider 經常使用方式 body: ChangeNotifierProvider.value( value: _model, child: ListView(children: _children), )); } } //字母按鈕 class CountItemWidget extends StatelessWidget { final String content; CountItemWidget({this.content}); @override Widget build(BuildContext context) { print("CountItemWidget:build"); return Container( height: 80, padding: EdgeInsets.all(15), alignment: Alignment.center, child: RaisedButton( onPressed: () => Provider.of<CountModel>(context, listen: false).increment(content), child: Selector<CountModel, int>( //從 CountModel獲得對應字母的count selector: (context, model) => model.contentMap[content], //若是先後兩次的count不相等,則刷新 shouldRebuild: (preCount, nextCount) => preCount != nextCount, builder: (context, count, child) { print("$content Selector:builder"); return Text("$content : $count"); }), ), ); } } 複製代碼
一個簡單的示例就完成啦。
咱們來看看Selector內部的實現。ide
當更新S時,比較先後S值是否相同。 不相同,則從新構建而且緩存住。相同,則使用cache的Widgetoop
//省略了部分代碼 typedef ShouldRebuild<T> = bool Function(T previous, T next); class Selector<A, S> extends Selector0<S> { Selector({ Key key, @required ValueWidgetBuilder<S> builder, //這部分代碼重點就是selector,Function參數,如何從A->S @required S Function(BuildContext, A) selector, ShouldRebuild<S> shouldRebuild, Widget child, }) : assert(selector != null), super( key: key, shouldRebuild: shouldRebuild, builder: builder, //寫成Provider.of<A>(context),更明確點 selector: (context) => selector(context, Provider.of(context)), child: child, ); } class Selector0<T> extends SingleChildStatefulWidget { final ValueWidgetBuilder<T> builder; final T Function(BuildContext) selector; final ShouldRebuild<T> _shouldRebuild; } class _Selector0State<T> extends SingleChildState<Selector0<T>> { //selector方法 A -> S T value; //若是不從新build,返回cache Widget Widget cache; //oldWidget --> Selector Widget oldWidget; @override Widget buildWithChild(BuildContext context, Widget child) { //實際調用過程,咱們從示例代碼的角度來看: CountModel是A,int是S //1.CountModel model=Provider.of<CountModel>(context); //2.int count = model.contentMap[content];count即爲selected final selected = widget.selector(context); //3.value==selected? value:preCount, selected:nextCount var shouldInvalidateCache = oldWidget != widget ||(widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected)) ||(widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected)); //4.不相等,則cache須要失效。 if (shouldInvalidateCache) { //5.生成當次的Widget,並cache住value和Widget value = selected; oldWidget = widget; cache = widget.builder( context, selected, child, ); } return cache; } } 複製代碼
經過上面的分析,咱們獲得一個流程圖
源碼分析
Selector相較於 Cosumer,提供了更細粒度的刷新控制。
某個組件只關心Model中某個數據變化,能夠考慮用Selector,即總體與局部。
特別須要指明的是selector的結果,必須是不可變的對象。 若是同一個對象,只是改變對象屬性,那shouldRebuild的兩個值永遠是相等的。
咱們在 前一篇Provider開發應用 提到的點贊帖子刷新單個的場景,一樣能夠用到Selector
post
class PostItemWidget2 extends StatelessWidget { final PostBean post; final void Function(BuildContext context, PostBean post) click; const PostItemWidget2({Key key, this.post, this.click}) : super(key: key); @override Widget build(BuildContext context) { print("PostItemWidget2:build"); return GestureDetector( onTap: () => click?.call(context, post), child: Container( height: 80, child: Row( children: <Widget>[ Expanded( child: Text( "${post?.content}", maxLines: 2, overflow: TextOverflow.ellipsis, ), ), Container( width: 50, child: Selector<PostItemChange, bool>( selector: (context, itemChange) => post.isLike, shouldRebuild: (pre, next) => pre != next, builder: (context, isLike, child) { return Icon( Icons.favorite, color: (isLike ?? false) ? Colors.red : Colors.grey, ); }), ), ], ), )); } } 複製代碼
正如 Selector庫所指出的
selector的結果,必須是不可變的對象
然而有時,UI層每每會直接使用數據層的對象,而不是轉成UI層對象。
例如咱們的例子中,PostItemWidget始終對應同一個PostBean。 先後區別就在於 PostBean.isLike發生了變化。同時咱們的刷新Notify只是簡單的 PostNotifier(id),並不會包含PostBean的全貌。
能夠參考Selector自定義一個Item Refresh。
下面這個能夠參考
//區別點 是否須要rebuild,是notifier與value結合判斷 typedef ShouldRebuild<A, T> = bool Function(A notifier, T value); class ItemRefresher<A, T> extends SingleChildStatefulWidget { final ShouldRebuild<A, T> _shouldRebuild; final T value; ItemRefresher({ Key key, //區別點 value有初始值, @required this.value, ShouldRebuild<A, T> shouldRebuild, @required this.builder, Widget child, }) : assert(builder != null), this._shouldRebuild = shouldRebuild, super(key: key, child: child); final ValueWidgetBuilder<T> builder; @override _ItemRefresherState<A, T> createState() => _ItemRefresherState<A, T>(); } class _ItemRefresherState<A, T> extends SingleChildState<ItemRefresher<A, T>> { Widget cache; Widget oldWidget; @override Widget buildWithChild(BuildContext context, Widget child) { //邏輯與selector相似 A notifier = Provider.of(context); var shouldInvalidateCache = oldWidget != widget || (widget._shouldRebuild != null && notifier != null && widget._shouldRebuild.call(notifier, widget.value)); if (shouldInvalidateCache) { oldWidget = widget; cache = widget.builder( context, widget.value, child, ); } return cache; } } 複製代碼
定義一個PostNotifier
class PostNotifier with ChangeNotifier { int id; } 複製代碼
構建PostItemWidget
Widget _buildListItem(BuildContext context, PostBean post) { return ItemRefresher<PostNotifier, PostBean>( value: post, //判斷是不是當前post shouldRebuild: (notifier, value) => (notifier.id != null && notifier.id == value.id), builder: (context, value, child) { return PostItemWidget( post: value, click: _skipPostDetail, ); }, ); // return PostItemWidget2(post: post, click: _skipPostDetail); } 複製代碼
全部的示例代碼都在這 Demo倉庫地址 入口:main_provider.dart -- Selector使用