flutter —— 深入理解 StatelessWidget 与 StatefulWidget 的 build 构建
前提知识:
setState 执行的是 Element 的 markNeedsBuild,将当前 element 加入标记列表。那么,标记完了,什么时候执行 element 的 rebuild呢?当渲染管线流程 WidgetsBinding.drawFrame 执行时,依次执行 buildScope,再执行 element.rebuild。(内容参考 渲染管理基本流程)
关键源码解析:
abstract class Widget extends DiagnosticableTree {
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
abstract class Element extends DiagnosticableTree implements BuildContext {
void markNeedsBuild() {
if (dirty) {
return;
}
_dirty = true;
owner!.scheduleBuildFor(this);
}
void rebuild({bool force = false}) {
if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
return;
}
performRebuild();
}
void performRebuild() {
_dirty = false;
}
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null) {
deactivateChild(child);
}
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
//当新的子组件 newWidget 与 旧的子组件 widget相等时,child 不变化。
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//当新的子组件 newWidget 与 旧的子组件 widget不相等,但 key 和 runtimeType 相同时,child 更新,执行其 update 方法。
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
// 执行 子element 的 update 方法
child.update(newWidget);
if (isTimelineTracked) {
FlutterTimeline.finishSync();
}
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
}
// ComponentElement 是 StatelessElement 与 StatefulElement 的 父element
abstract class ComponentElement extends Element {
// 关键!!!
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
// StatefulElement overrides this to also call state.didChangeDependencies.
rebuild(); // This eventually calls performRebuild.
}
@override
void performRebuild() {
Widget? built;
try {
// 关键!!!
// 对于 ComponentElement,element 的 rebuild 总是伴随 build 的执行。
built = build();
} catch (e, stack) {
} finally {
// 在调用 build() 之后,才将元素标记为干净。
// 所以在 build() 期间尝试标记 NeedsBuild() 将被忽略,而不报错。
super.performRebuild(); // clears the "dirty" flag
}
try {
// 关键!!!
// updateChild 方法中执行 子element 的 update 方法
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(
_reportException(
ErrorDescription('building $this'),
),
);
_child = updateChild(null, built, slot);
}
}
}
//StatelessElement 继承自 ComponentElement,并重写了 element 的 update 方法
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget super.widget);
@override
Widget build() => (widget as StatelessWidget).build(this);
// 父 element 执行 rebuild 会递归判断子组件是否需要更新,若更新则执行 子element 的 update 方法
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
// 对于 StatelessElement,当前 element 进行 rebuild
rebuild(force: true);
}
}
//StatefulElement 继承自 ComponentElement,并重写了 element 的 update 方法 和 ComponentElement 的 _firstBuild 方法
class StatefulElement extends ComponentElement {
@override
Widget build() => state.build(this);
@override
void reassemble() {
if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
state.reassemble();
}
super.reassemble();
}
@override
void _firstBuild() {
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
return true;
}());
state.didChangeDependencies();
super._firstBuild();
}
@override
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
// 父 element 执行 rebuild 会递归判断子组件是否需要更新,若更新则执行 子element 的 update 方法
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
state._widget = widget as StatefulWidget;
// 对于 StatefulElement,先执行 state.didUpdateWidget
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
// 对于 StatefulElement,当前 element 进行 rebuild
rebuild(force: true);
}
@override
void activate() {
super.activate();
state.activate();
markNeedsBuild();
}
@override
void deactivate() {
state.deactivate();
super.deactivate();
}
@override
void unmount() {
super.unmount();
state.dispose();
state._element = null;
_state = null;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
}
总结:
针对 ComponentElement,有几个特性:
1)在 mount 后会执行 build 方法。不同于普通的 element,① ComponentElement 重写了 mount 方法。在 mount 后会执行 _firstBuild 方法。对于 StatefulElement 在 _firstBuild 中还会执行其 state.initState 与 state.didChangeDependencies 方法。② _firstBuild 后执行 ③ rebuild 方法,再执行 ④ performRebuild 方法。ComponentElement 重写了 performRebuild 方法,⑤ 执行 build 方法。
2)在 setState 后会执行 build 方法。setState 导致 widget 重建,经过一些步骤,最终会执行 ③ rebuild 方法。同上,再执行 ④ performRebuild 方法,最后 ⑤ 执行 build 方法。
3)如果父组件 setState 后,子组件会 rebuild 吗?在执行 ④ performRebuild 方法时,父element 还会根据 build 生成的子组件,判断 子element 是否需要更新,如果需要更新(组件不相等,但 key 和 runtimeType 相同),则执行其 update 方法。如果 子element 是 StatelessElement,则执行该 子element 的 rebuild 方法;如果是 StatefulElement,则先执行该 子element 的 didUpdateWidget 后执行其 rebuild 方法。
注1:判断新旧子组件是否相等的条件:子组件在 build 时是否加了 const 前缀,如果没有,则不相等,会 rebuild。
class StatelessTestScreen extends StatelessWidget {
const StatelessTestScreen({super.key});
@override
Widget build(BuildContext context) {
// 加了 const,父组件 rebuild 时,DemoScreen 不会 rebuild
return const DemoScreen();
}
}
注2:判断新旧子组件是 更新 update 还是 重新创建 newChild,主要依据 key 与 runtimeType。而 key 默认是 null,在类不变的情况下,通常是更新,而不是重新创建。如果希望组件 rebuild 时重新创建,则可以手动加一个 key,一但 key 变更则会在下次 rebuild 时重新创建。
Widget build(BuildContext context) {
// 使用 UniqueKey() 作为 key,每次 rebuild 都会重新创建,而不会更新。
return DemoScreen(key: UniqueKey());
}
注3:针对注1的补充,组件不进行 rebuild 不代表其 RenderObject 不会重绘,请查看 深入理解 RenderObject 布局与绘制
2333