provider在新版本中添加了selector,整理一下selector使用时候会踩的坑git
provider早期版本中只有Consumer,当model中调用notifyListeners()的时候,对应的Consumer的视图就会整个rebuild,而在3.x版本以后出现Selector能够仅选择model中某个值来最小范围的刷新视图,而且在4.0版本以后会对使用的值进行deep check,也能够本身自定义shouldRebuild。github
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider<TestModel>(
create: (_) => TestModel(),
child: Consumer<TestModel>(builder: (ctx, testModel, child) {
debugPrint('Consumer build');
return Column(
children: <Widget>[
Text(testModel.string1),
RaisedButton(onPressed: () {
Provider.of<TestModel>(ctx, listen: false).changeString2();
}, child: Text('change'),),
Selector<TestModel, String>(
selector: (_, testModel) => testModel.string2,
builder: (_, str, c) {
debugPrint('string2 build');
return Text(str);
},
)
],
);
}),
),
);
}
}
复制代码
修改后缓存
ChangeNotifierProvider<TestModel>(
create: (_) => TestModel(),
child: Selector<TestModel, String>(selector: (_, testModle) => testModle.string1 , builder: (ctx, strint1, child) {
debugPrint('Consumer build');
return Column(
children: <Widget>[
Text(strint1),
RaisedButton(onPressed: () {
Provider.of<TestModel>(ctx, listen: false).changeString2();
}, child: Text('change'),),
Selector<TestModel, String>(
selector: (_, testModel) => testModel.string2,
builder: (_, str, c) {
debugPrint('string2 build');
return Text(str);
},
)
],
);
}),
)
复制代码
如上修改后,当model中string2变化的时候,就只会rebuild string2的视图,string1并不会rebuild。less
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider<TestModel>(
create: (_) => TestModel(),
child: Consumer<TestModel>(builder: (ctx, testModel, child) {
debugPrint('Consumer build');
return Column(
children: <Widget>[
Text(testModel.string1),
RaisedButton(onPressed: () {
Provider.of<TestModel>(context, listen: false).changeString2();
}, child: Text('change'),),
Selector<TestModel, String>(
selector: (_, testModel) => testModel.string2,
builder: (_, str, c) {
debugPrint('string2 build');
return Text(str);
},
)
],
);
}),
),
);
}
}
复制代码
如上代码是会报 Could not find the correct Provider above this TestPage Widget的错误,修改成 Provider.of(ctx, listen: false).changeString2(), 关于context问题请查阅相关资料,这里再也不赘述。同时在selected中注意设置listen为false。dom
dart中List等属于引用类型对象,和js同样。由此当使用selector选择的值为引用类型对象的时候,须要特别注意。ide
ChangeNotifierProvider<TestModel>(
create: (_) => TestModel(),
child: Selector<TestModel, List>(
selector: (_, testModel) => testModel.numberList,
shouldRebuild: (prev, next) => prev.first != next.first,
builder: (ctx, numberList, child) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: () {
Provider.of<TestModel>(ctx, listen: false).changeNumber();
},
child: Text(numberList.first.toString()),
),
Text(numberList.last.toString())
],
);
}),
)
复制代码
changeNumber() {
numberList.first = Random().nextInt(10);
debugPrint(numberList.first.toString());
notifyListeners();
}
复制代码
在某种场景下,可能有时候会和如上代码所示同样但愿selected一个引用对象,仅但愿在引用对象中某个值变化的时候rebuild视图,然而会发现经过上面的代码会发现numberList.first已经改变,可是视图不会rebuild。就算是取消自定义的shouldRebuild使用源码中的deep check也依然不会rebuild。ui
看下Selector源码this
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
T value;
Widget cache;
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
final selected = widget.selector(context);
var shouldInvalidateCache = oldWidget != widget ||
(widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected)) ||
(widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected));
if (shouldInvalidateCache) {
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
return cache;
}
}
复制代码
能够发现selector会缓存selected的值并保存到value,若是seleted的值为引用对象的时候,value和selected指向同一个引用地址,当调用changeNumber的时候,修改了numberList的值,可是引用地址没变,缓存的value中的值也随着发生变化,因此在_shouldRebuild的时候对比的value和selected一直是同样的,视图也就不会rebuild。spa
官方在缓存value的时候并无使用深拷贝,而是推荐选择的值为immutabledebug