首先看一个例子:
class HomePage extends StatefulWidget {
HomePageState createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new ChildWidget(),
RaisedButton(
child: Text("刷新"),
onPressed: () => setState(() {}),
),
],
);
}
}
class ChildWidget extends StatefulWidget {
ChildWidget() {
developer.log('ChildWidget init', name: "main.dart");
}
@override
State<StatefulWidget> createState() {
developer.log('ChildWidget createState', name:"main.dart");
return new ChildWidgetState();
}
}
class ChildWidgetState extends State<ChildWidget> {
@override
Widget build(BuildContext context) {
developer.log('ChildWidgetState build', name: "main.dart");
return Text('111');
}
}
代码很简单,在HomePageState buld函数中,点击刷新按钮,调用setState()方法,build方法再次执行,new ChildWidget()执行,重新new出了一个ChildWidget对象。
Debug程序,点击刷新按钮,看输出日志: 可以看到,ChildWidget createState并没有输出。也就是说,ChildWidgetState对象并没有没有重新创建。这是为什么?
首先了解两个基本概念:
1. Widget实际上就是Element的配置数据,Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成;不过,由于Element是通过Widget生成的,所以它们之间有对应关系,在大多数场景,我们可以宽泛地认为Widget树就是指UI控件树或UI渲染树。
2.一个Widget对象(实例)可以对应多个Element对象(实例)。这很好理解,根据同一份配置(Widget),可以创建多个实例(Element)。
就像同一个Widget被插入Widget树的不同位置,就行成了两个Element。
Elment是真正渲染到屏幕上的(通过RenderObject),createState()方法什么时候调用的?来看源码:
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatefulElement(StatefulWidget widget)
: _state = widget.createState(), //关键代码
super(widget) {
//省略...
}
//省略...
}
可以看出只有当StatefulElement构造的时候,createState()方法才会调用! 也就是说,点击刷新按钮的时候,Element对象并没有重新构造。Element需不需要重新构造,是谁决定的?是它的配置Widget决定的,看一下Widget源码中决定Elment是否需要重新构造的函数:
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
@protected
Element createElement();
//关键代码
static bool canUpdate(Widget oldWidget, Widget newWidget) {
bool value = oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
return value;
}
}
canUpdate()是一个静态方法,它主要用于在Widget树重新build时复用旧的widget,其实具体来说,应该是:是否用新的Widget对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidget与oldWidget的runtimeType和key同时相等时就会用newWidget去更新Element对象的配置,否则就会创建新的Element。
Widget中静态函数canUpdate函数决定Element是否需要重新构造,newWidget与oldWidget的runtimeType和key同时相等时就会用newWidget去更新Element对象的配置,否则就会创建新的Element。 所以当点击刷新按钮的时候,new ChildWidget(),newWidget的runtimeType,key和oldWidget的runtimeType,key都是相等的,这意味着不需要重新去创建Element,只要更新Element就可以了。所以createState()函数也就没有执行。
Element不变,State不变!
参考链接:
- juejin.cn/post/684490… (Flutter完整开发实战详解十五、全面理解State与Provider)
- book.flutterchina.club/chapter3/fl… (Widget简介)
- juejin.cn/post/684490… (Flutter | 深入浅出Key)
- www.jianshu.com/p/af9705509… (Flutter更新机制分析)