flutter —— 深入理解 StatelessWidget 与 StatefulWidget 的 build 构建

发布时间 2023-09-03 02:45:22作者: Lemo_wd

前提知识:

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.
  }

  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());
  }

2333