进入这个帖子,能够先看看前面的介绍哈。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使用