1.setState()
干了啥?
@protected
void setState(VoidCallback fn) {
//调用我们传入的函数
final dynamic result = fn() as dynamic;
//当前的element执行markNeedsBuild方法
_element.markNeedsBuild();
}
/// owner The object that manages the lifecycle of this element.
/// BuildOwner owner负责管理所有element的构建以及生命周期
void markNeedsBuild() {
//element将自己标记为脏
_dirty = true;
//scheduleBuildFor 译:计划构建
owner.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
onBuildScheduled!();
//将element添加到BuildOwner的_dirtyElements集合中
_dirtyElements.add(element);
//当前element已在脏列表中属性设置为true
element._inDirtyList = true;
}
- 小结:setState()就是将当前的
element
标记成脏
然后交由BuildOwner
,并加入到BuildOwner
的_dirtyElements
脏列表中。
2.那页面是如何更新的呢?
@override
void drawFrame() {
try {
if (renderViewElement != null)
// buildOwner就是前面提到的负责管理widgetbuild的对象
// This is initialized the first time [runApp] is called.
// 这里的renderViewElement是整个UI树的根节点
buildOwner.buildScope(renderViewElement);
super.drawFrame();
//将不再活跃的节点从UI树中移除
buildOwner.finalizeTree();
} finally {
/·················/
}
}
void buildScope(Element context, [ VoidCallback callback ]) {
...
//取出脏elements调用rebuild() rebuild方法最终会触发performRebuild
final Element element = _dirtyElements[index];
element.rebuild();
...
}
void performRebuild() {
Widget built;
//这里解释了为啥setState会触发build方法
built = build();
//执行updateChild操作(即更新得到最新的子节点)
//_child为当前element的子element,built则是build后element最新当前的widget
_child = updateChild(_child, built, slot);
}
//setState会触发build方法的原因
class StatefulElement extends ComponentElement {
...
Widget build() => state.build(this);
...
//child为子element newWidget为re->build后新的当前element的widget
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
//新的子节点是空的 老的是有子节点 则清除子节点即可
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
//❗️新的节点和老的是同一个Widget实例 则return newChild;
if (hasSameSuperclass && child.widget == newWidget) {
newChild = child;
e.g 类似这种情况 返回的都是同一个实例化后的widget
class _Flutter_Test_Widget_LifecycleState
extends State<Flutter_Test_Widget_Lifecycle> {
bool a = true;
HeHe he = HeHe("HEHE");
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
a = !a;
});
},
child: a == true
? he
: he,
);
}
}
//❗️❗️符合这种情况则是child.widget和newWidget是同一类型,但是不是同一个widget实例
//则直接进行child.update操作,直接进行子节点的子节点的更新操作(A child B,B child C, 当前是A 判断到B是同类型但是不同实例,则B进行update操作去触发B的rebuild操作, A则不会执行inflateWidget来对B进行操作,而是对B的子节点进行操作-性能优化)
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
child.update(newWidget);
//❗️❗️❗️widget既不符合同一个实例widget也不符合同一类型的weidget,则移除老节点,触发inflateWidget,重新构建子节点
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
//❗️❗️❗️❗️如果child为空 即老节点为空子节点不为空
newChild = inflateWidget(newWidget, newSlot);
//文字表述伪代码 表示各种情况
如果之前的位置child为null
A、如果newWidget为null的话,说明这个位置始终没有子节点,直接返回null即可。
B、如果newWidget不为null,说明这个位置新增加了子节点调用inflateWidget(newWidget, newSlot)生成一个新的Element返回
如果之前的child不为null
C、如果newWidget为null的话,说明这个位置需要移除以前的节点,调用 deactivateChild(child)移除并且返回null
D、如果newWidget不为null的话,先调用Widget.canUpdate(child.widget, newWidget)对比是否能更新。
❗️❗️❗️❗️这个方法会对比两个Widget的runtimeType和key,如果一致则说明子Widget没有改变,
❗️❗️❗️❗️只是需要根据newWidget(配置清单)更新下当前节点的数据child.update(newWidget);
如果不一致说明这个位置发生变化,则deactivateChild(child)后返回inflateWidget(newWidget, newSlot)
}
//根据newWidget(配置清单)更新下当前节点的数据的update 会触发`rebuild();`
void update(StatefulWidget newWidget) {
super.update(newWidget);
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
//这个rebuild则是会再次触发`performRebuild()`,进而`updateChild()`,于是子节点再走这样的一个流程,子节点的子节点再走这样的一个流程,不断的递归直到页面的最子一级节点
rebuild();
}
开始FrameWork层会通知Engine表示自己可以进行渲染了,在下一个Vsync信号到来之时,Engine层会通过Windows.onDrawFrame回调Framework进行整个页面的构建与绘制。每次收到渲染页面的通知后,Engine调用Windows.onDrawFrame最终交给_handleDrawFrame()方法进行处理。最后会走到WidgetsBinding.drawFrame()
=>buildOwner.buildScope(renderViewElement)
=>_dirtyElements[index].rebuild()
=>performRebuild()
这里会触发当前element的widget的build方法=>updateChild()
注意这里已经是子节点进行接下来的操作了=>子节点update()
=>子节点rebuild()
=>子节点performRebuild()
...
小结:所以说在widget树中,越高层的build()
里调用setState()
会导致遍历所有的子节点=>遍历所有子节点的子节点...
话术总结:setState()
会将当前的element
标记为脏
,并交由buildOwner
,由buildOwner
加入自己的脏列表中
,等收到页面渲染的通知后(这里流程简略掉),会调用buildOwenr. buildScope ()
,这里会遍历脏列表
然后每一个都会调用rebuild()
,rebuild()
又会调用performRebuild()
,performRebuild()
则会调用build()
方法重建当前的element
,然后调用updateChild ()
开始更新子节点,进而触发子节点的rebuild()
方法,进行下一轮的周期...一直到最后一个节点
网友评论