1.setState的原理
setstate会触发canUpdate函数的调用,这个函数会对比old widget和new widget的对应元素的runtimeType和key,如果两个都相同则根据new widget调用build方法刷新Element,如果有不同则替换Element不调用build方法。更新时RenderobjectElement会用新的widget的key在老的Element列表里查找,如果有匹配的会直接用已有的Element替换,如果没有则会重新创建。key能够最大程度实现节点重用,特别是在列表里的widget交换的时候。
2.Key
flutter里的key包括两大类,LocalKey和GlobalKey。被GlobalKey标记的组件能在整个组件树里实现元素重用(不被unmount的情况下),被LocalKey标记的组件只能在同一直接父节点下有重用效果(不被unmount的情况下)。如在MultiChildRenderObjectWidget类型的widget里,子widget是列表,如若要通过交换组件方式实现元素交换,则需要在元素最上层添加key。比如在很多讲key的文章里引用到的交换颜色块的例子:
//封装一个StatefulContainer的组件
class StatefulContainer extends StatefulWidget {
//final Color color = RandomColor().randomColor();
StatefulContainer({Key key}) : super(key: key);
@override
_StatefulContainerState createState() => _StatefulContainerState();
}
class _StatefulContainerState extends State<StatefulContainer> {
final Color color = RandomColor().randomColor();
@override
Widget build(BuildContext context) {
print('_StatefulContainerState build');
return Container(
width: 100,
height: 100,
color: widget.color,
);
}
@override
void dispose(){
print('_StatefulContainerState dispose');
super.dispose();
}
}
//主视图调用StatefulContainer
class Screen extends StatefulWidget {
@override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
List<Widget> widgets = [
StatefulContainer(),
StatefulContainer(),
];
@override
void dispose(){
print('_ScreenState dispose');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widgets,
),
),
floatingActionButton: FloatingActionButton(
onPressed: switchWidget,
child: Icon(Icons.undo),
),
);
}
switchWidget(){
widgets.insert(0, widgets.removeAt(1));
setState(() {});
}
}
UniqueKey是LocalKey的一种,这里我们使用UniqueKey。点击交换按钮实现组件交换,如果不在StatefulContainer上加上Key的话无法实现两个色块颜色交换,如果加上UniqueKey后正常。这个例子说明UniqueKey能在拥有同一直接父节点时做不更改父节点的交换位置的操作时可保存状态并且元素可被重用,此时稍作改动,按钮点击后作将Row替换为另一个Widget,该Widget引用Row里面的元素试试,
class _ScreenState extends State<Screen> {
List<Widget> widgets = [
StatefulContainer(key:UniqueKey()),
StatefulContainer(key:GlobalKey()),//可观察该key是GlobalKey和UniqueKey时的差别
];
Widget wid;
@override
void dispose(){
print('_ScreenState dispose');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: wid!=null?wid:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widgets,
),
),
floatingActionButton: FloatingActionButton(
onPressed: switchWidget,
child: Icon(Icons.undo),
),
);
}
switchWidget(){
wid = widgets.removeAt(1);
setState(() {});
}
}
如果widgets第二个元素的key类型是GlobalKey(),日志:
flutter: _StatefulContainerState initState
flutter: _StatefulContainerState build
flutter: _StatefulContainerState initState
flutter: _StatefulContainerState build
flutter: _StatefulContainerState build
flutter: _StatefulContainerState dispose
key类型是UniqueKey(),
flutter: _StatefulContainerState initState
flutter: _StatefulContainerState build
flutter: _StatefulContainerState initState
flutter: _StatefulContainerState build
flutter: _StatefulContainerState initState
flutter: _StatefulContainerState build
flutter: _StatefulContainerState dispose
flutter: _StatefulContainerState dispose
wid为空的时候就显示Row,点击后移除List的一个元素并将让wid变量引用它。这个例子可以对比Localkey和GlobalKey的不同,如果移除的元素是UniqueKey的话,该StatefulContainer被改变了父节点则会被unmount掉(即调用dispose),此时重新展示的wid是新创建的,如果移除的元素添加的是GlobalKey的话,则并没有被unmount(dispose没有被调用),该元素会被重用,会重新调用build方法。原因是LocalKey引用的元素在状态改变并且父节点未做改变的情况下可以被重用,但是在父节点被修改后则失效。GlobalKey不管父节点是否改变状态都可以重用。但是在实际使用中要尽量减少GlobalKey的使用,因为GlobalKey里使用的全局的静态变量Map保存的GlobalKey和Element的对应关系,app整个生命周期都会存在,比较耗空间和性能