一言不合setState!
setState可以分为两个部分:
- 将element标脏
- 渲染时将所有脏element都rebuild,且将自己的child进行update
先看第一步
去掉assert之后的setState只有两步:
- 调用放进来的function
- 将当前element标记needBuild
//class State
@protected
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element!.markNeedsBuild();
}
接着看markNeedsBuild,同样去掉assert,同样只有两步:
- 将dirty置为true
- 将自己作为参数,调用BuildOwner.scheduleBuildFor
//class Element
void markNeedsBuild() {
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
这个owner从哪来?owner又是啥?scheduleBuildFor是啥作用?,我们看下Element的mount方法:
//class Element
@mustCallSuper
void mount(Element? parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
final Key? key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
}
可以看到入参是parent(即该节点的父节点),newSlot(即该节点即将插入的插槽),mount
方法完成了:
- 链接了当前节点和它的父节点
- 并且插入插槽
- 接着承接了父节点传下来的「owner」
找到BuildOwner
类的注释:
/// class BuildOwner
/// Manager class for the widgets framework.
///
/// This class tracks which widgets need rebuilding, and handles other tasks
/// that apply to widget trees as a whole, such as managing the inactive element
/// list for the tree and triggering the "reassemble" command when necessary
/// during hot reload when debugging.
///
/// The main build owner is typically owned by the [WidgetsBinding], and is
/// driven from the operating system along with the rest of the
/// build/layout/paint pipeline.
///
/// Additional build owners can be built to manage off-screen widget trees.
///
/// To assign a build owner to a tree, use the
/// [RootRenderObjectElement.assignOwner] method on the root element of the
/// widget tree.
简单点总结就是「BuildOwner主要用于widget的rebuild,并且由WidgetBingding
所有」,接下来找到WidgetBinding
/// class WidgetBinding
/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
// Initialization of [_buildOwner] has to be done after
// [super.initInstances] is called, as it requires [ServicesBinding] to
// properly setup the [defaultBinaryMessenger] instance.
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
}
可以在initInstances()
方法里面看到实例化了BuildOwner
。
视角继续追踪BuildOwner.scheduleBuildFor
方法(即Element.markNeedsBuild
里面的主要代码):
///class BuildOwner
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
最后几行代码,才是真的,将element标脏_dirtyElements.add(element);element._inDirtyList = true;
最后一个方法BuildOwner.buildScope
,先看关键的注释,其他省略:
/// Establishes a scope for updating the widget tree, and calls the given
/// `callback`, if any. Then, builds all the elements that were marked as
/// dirty using [scheduleBuildFor], in depth order.
机翻:
在一定的范围内更新widget树,并且调用他们的callback,然后将所有通过scheduleBuildFor标记成脏的element通过深度顺序,进行build。
即,是在这里发出了rebuild的指令
我们再看下源码:
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
Element? debugPreviousBuildTarget;
_dirtyElementsNeedsResorting = false;
try {
callback();
}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
}finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
}
保留了关键的代码块,可以看到在buildScope
中,遍历了脏的eleement,调用他们的rebuild方法,然后mark回干净。
小结:在WidgetsBinding
初始化时,会创建一个BuildOwner
对象。每一个Element
在挂载上树时(即调用mount方法),都会将上述提到的BuildOwner
传递给自己的子节点,每次setState调用的时候,element就把自己add进BuildOwner._dirtyElements
中,等下一帧渲染时,BuildOwner.buildScope
就会遍历调用所有脏的Element.rebuild
。
第二步:
setState是如何调用子节点的rebuild的?
Element.rebuild
方法源码:
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
/// called to mark this element dirty, by [mount] when the element is first
/// built, and by [update] when the widget has changed.
void rebuild() {
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
Element? debugPreviousBuildTarget;
performRebuild()
}
只有一个关键代码,调用performRebuild
,接着看performRebuild
源码。
StatefulWidget和StatelessWidget都override了createElement方法,分别返回StatefulElement和StatelessElement,我们看他们的父类ComponentElement
,ComponentElement.performRebuild方法:
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
///
/// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating.
@override
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
Widget? built;
built = build();
debugWidgetBuilderValue(widget, built);
_child = updateChild(_child, built, slot);
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
- 调用了build方法,获得了built
- 调用updateChild方法,更新_child
接着看Element.updateChild
方法
/// Update the given child with the given new configuration.
///
/// This method is the core of the widgets system. It is called each time we
/// are to add, update, or remove a child based on an updated configuration.
///
/// The `newSlot` argument specifies the new value for this element's [slot].
///
/// If the `child` is null, and the `newWidget` is not null, then we have a new
/// child for which we need to create an [Element], configured with `newWidget`.
///
/// If the `newWidget` is null, and the `child` is not null, then we need to
/// remove it because it no longer has a configuration.
///
/// If neither are null, then we need to update the `child`'s configuration to
/// be the new configuration given by `newWidget`. If `newWidget` can be given
/// to the existing child (as determined by [Widget.canUpdate]), then it is so
/// given. Otherwise, the old child needs to be disposed and a new child
/// created for the new configuration.
///
/// If both are null, then we don't have a child and won't have a child, so we
/// do nothing.
///
/// The [updateChild] method returns the new child, if it had to create one,
/// or the child that was passed in, if it just had to update the child, or
/// null, if it removed the child and did not replace it.
///
/// The following table summarizes the above:
///
/// | | **newWidget == null** | **newWidget != null** |
/// | :-----------------: | :--------------------- | :---------------------- |
/// | **child == null** | Returns null. | Returns new [Element]. |
/// | **child != null** | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
///
/// The `newSlot` argument is used only if `newWidget` is not null. If `child`
/// is null (or if the old child cannot be updated), then the `newSlot` is
/// given to the new [Element] that is created for the child, via
/// [inflateWidget]. If `child` is not null (and the old child _can_ be
/// updated), then the `newSlot` is given to [updateSlotForChild] to update
/// its slot, in case it has moved around since it was last built.
///
/// See the [RenderObjectElement] documentation for more information on slots.
@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
// When the type of a widget is changed between Stateful and Stateless via
// hot reload, the element tree will end up in a partially invalid state.
// That is, if the widget was a StatefulWidget and is now a StatelessWidget,
// then the element tree currently contains a StatefulElement that is incorrectly
// referencing a StatelessWidget (and likewise with StatelessElement).
//
// To avoid crashing due to type errors, we need to gently guide the invalid
// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
// returns false which prevents us from trying to update the existing element
// incorrectly.
//
// For the case where the widget becomes Stateful, we also need to avoid
// accessing `StatelessElement.widget` as the cast on the getter will
// cause a type error to be thrown. Here we avoid that by short-circuiting
// the `Widget.canUpdate` check once `hasSameSuperclass` is false.
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
这个方法的备注非常详细。
- 当newWidget==null&&child==null时,return null。因为该组件原本为null,然后执行build之后还是生成null,那只能返回null
- 当newWidget==null&&child≠null时,将当前child的自己deactivate掉。因为执行build生成了null,要替换掉原来≠null的child,那么就要把child以及它的所有子节点都要失效否则会一直占着
- 当newWidget≠null&&child==null时,那就通过inflateWidget获取newWidget的element,然后返回该element。因为执行build生成的≠null,但是原本child为null,自然使用newWidget的。
- 当newWidget≠null&&child≠null时,需要让child的配置更新为当前的新widget。如果新widget可以用于更新child,那么,更新。否则,删除孩子,使用新配置生成新孩子。(是否可以更新child,由Widget.canUpdate方法来判断)
可以看到在Element.updateChild
中,关键的点在于child.update(newWidget);
也就是Element.update
由于Element
的update
方法是空的,我们直接看StatefulElement.update
源码:
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
// We mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
可以看到StatefulElement.update
里面也调用了rebuild。到这里,一切都能串起来了。
首先是element
挂载(mount
时,获取parent
传下来的buildOwner
)到树上,然后在setState的时候,把自己标记脏(BuildOwner._dirtyElements.add
),接着是等待下一次渲染来临时,BuildOwner
会对_dirtyElements
中每一个element都调用rebuild
方法,而rebuild
方法的关键,就是Element.performRebuild
。在Element.performRebuild
里面,更新了自己(调用了自己的build
),还更新了自己的孩子(调用了Element.updateChild
),接着Element.updateChild
的核心是child.update
。最后查看StatefulElement.update
方法,其实最后还是调用了Element.rebuild
。
一句话总结就是,父组件setState时,既更新自己,又更新孩子,而孩子又执行和父组件一样的动作,一直往树的最底层执行。所以,在做setState时,如果能降低刷新的成本,会更好,比如使用局部刷新组件ValueNotifier等。
最后附上流程图
图例对应的源代码:
class ColorBox extends StatefulWidget {
const ColorBox({Key? key, required this.color}) : super(key: key);
final Color color;
@override
State<ColorBox> createState() => _ColorBoxState();
}
class _ColorBoxState extends State<ColorBox> {
int count = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
count++;
setState(() {});
},
child: Container(
color: widget.color,
width: 100,
height: 100,
child: Center(
child: Text(
count.toString(),
style: TextStyle(fontSize: 30),
),
),
),
);
}
}
class TestPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Widget> widgetList = [
ColorBox(
color: Colors.yellow,
),
ColorBox(color: Colors.blue)
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("key test"),
),
body: Container(
padding: EdgeInsets.all(20),
child: Column(
children: [...widgetList],
),
),
floatingActionButton: GestureDetector(
onTap: () {
widgetList = widgetList.reversed.toList();
setState(() {});
},
child: Icon(
Icons.add,
),
));
}
}
图例对应的UI:
第一次写,有疑问或错误的地方欢迎指出