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