StatelessWidget、StatefulWidget中的build

前言

Flutter中构建页面, 需要用到Widget组件, Widget又分为无状态组件StatelessWidget, 有状态组件StatefulWidget。

无状态组价StatelessWidget,就是说一旦这个Widget创建完成,状态就不允许再变动。

有状态组件StatefulWidget,就是说当前Widget创建完成之后,还可以对当前Widget做更改,可以通过setState函数来刷新当前Widget来达到有状态。

StatelessWidget

那么, 无状态组件StatelessWidget的创建流程是怎样的呢?StatelessWidget的源码如下:

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key? key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

  considerations.
  @protected
  Widget build(BuildContext context);
}

复制代码

由StatelessWidget源码, 我们可以知道:

  • StatelessWidget是个抽象类, 继承Widget。
  • 提供了一个构造器方法const StatelessWidget({ Key? key }) : super(key: key)
  • 重写了创建Element方法createElement
  • 提供了一个开发者常见的Widget build(BuildContext context)方法。

StatelessElement

widget实例调用createElement, 传入当前widget对象, 创建elment对象。接下来, 我们看一下StatelessElement源码:

/// An [Element] that uses a [StatelessWidget] as its configuration.
class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}
复制代码

从上面的源码注释、源码、及继承关系可以看出:

  • An [Element] that uses a [StatelessWidget] as its configuration. 这个使用StatelessWidget对象提供的信息做为StatelessElement的配置。
  • StatelessElement继承ComponentElement
  • StatelessElement的初始化方法, 接收外部传进来的StatelessWidget对象。
  • 当前StatelessElement对象持有外部传进来的widget对象
  • 重写了ComponentElement提供的build方法。当element对象执行这个build方法时, 实际上外部传进来的widget对象, 调用了widget.build(this);方法, 也就是我们在StatelessWidget需要实现的Widget build(BuildContext context)方法。
  • 重写了Element提供的update方法, 更新widget时, 调用。

从上面的源码分析知道一个非常重点的知识, 即我们在StatelessWidget类中必须实现的Widget build(BuildContext context)方法, 其中BuildContext context就是element对象。那么, 我常见的build方法是什么时机触发的呢?带着问题, 我们一起分析下ComponentElement源码。

ComponentElement

abstract class ComponentElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  ComponentElement(Widget widget) : super(widget);

  Element? _child;

  bool _debugDoingBuild = false;
  @override
  bool get debugDoingBuild => _debugDoingBuild;

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_lifecycleState == _ElementLifecycle.active);
    _firstBuild();
    assert(_child != null);
  }

  void _firstBuild() {
    rebuild();
  }

  /// 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
  @pragma('vm:notify-debugger-on-exception')
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget? built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      built = build();
      assert(() {
        _debugDoingBuild = false;
        return true;
      }());
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

  /// Subclasses should override this function to actually call the appropriate
  /// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
  /// their widget.
  @protected
  Widget build();

  @override
  void visitChildren(ElementVisitor visitor) {
    if (_child != null)
      visitor(_child!);
  }

  @override
  void forgetChild(Element child) {
    assert(child == _child);
    _child = null;
    super.forgetChild(child);
  }
}
复制代码

ComponentElement继承Element, 我们可以知道如下几个重点知识:

  • ComponentElement对象在mount时, element对象的生命周期即处于_ElementLifecycle.active 活跃状态, 之后调用了_firstBuild方法。
  • _firstBuild调用了Element提供的rebuild方法。
  • ComponentElement重写了Element提供的performRebuild方法。
  • Element的源码可知, Element的生命周期处于_ElementLifecycle.active活跃状态后, Element对象会去执行performRebuild()
  • performRebuild方法, 执行了build方法。
  • build方法调用了StatelessWidget提供的widget.build(this)方法, 返回widget对象。也就是开发者常打交道的方法。

由上面重点知识, 我们了解了StatelessWidgetbuild方法的执行过程, 并且知道build(BuildContext context)context就是element对象。

特别地, 我们需要重中之重地关注ComponentElement重写了void performRebuild()方法。这个方法的官方注释, 清晰可见地说明, 它会去取唤醒StatelessWidget对象中的build方法、State.build中的build方法, 去更新它们的tree。同时, 它在element对象mount, 并处于活跃状态状态后, 会自动地产生第一次build过程。以后当elemnt需要更新时, 调用rebuild, 会执行这个performRebuild方法。

Element

由此观之, 源码的调用顺序是, Element.rebuild() -> ComponentElement.performRebuild() -> ComponentElement.build(),那么 Element.build()  是谁调用的?

这个在它的注释中有写: Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor],也就是由 BuildOwner 调用的(BuildOwner 在 element tree 中是唯一的,它保存在 WidgetsBinding.instance.buildOwner 中)。

那么这个 BuildOwner.scheduleBuildFor(..) 是如何触发的呢?答案在Element.markNeedsBuild(..)

我们看下Element源码:

abstract class Element extends DiagnosticableTree implements BuildContext {
    
    void markNeedsBuild() {
      assert(_lifecycleState != _ElementLifecycle.defunct);
      if (_lifecycleState != _ElementLifecycle.active)
        return;
      assert(owner != null);
      assert(_lifecycleState == _ElementLifecycle.active);
      
      .......
      
      if (dirty)
        return;
        
     // 记脏, 也就是需要更新
      _dirty = true;
      
     // 可以看到把 element 自身加入到了 [BuildOwner] 中
      owner!.scheduleBuildFor(this);
    }

}
复制代码

Element的markNeedsBuild方法里面, 执行调用了owner!.scheduleBuildFor(this);方法, 将element对象添加到了owner中。

BuildOwner

接下来, 我们看下BuildOwner的源码:

class BuildOwner {
    
    BuildOwner({ this.onBuildScheduled, FocusManager? focusManager }) :
        focusManager = focusManager ?? (FocusManager()..registerGlobalHandlers());

    /// Adds an element to the dirty elements list so that it will be rebuilt
    /// when [WidgetsBinding.drawFrame] calls [buildScope].
    void scheduleBuildFor(Element element) {
      assert(element != null);
      assert(element.owner == this);

      ......

      if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      // 表明element已经在_dirtyElements列表了
        _scheduledFlushDirtyElements = true;
        onBuildScheduled!();
      }

      // 加入到 dirty 列表
      _dirtyElements.add(element);
      element._inDirtyList = true;
    }


    void buildScope(Element context, [ VoidCallback callback ]) {
           // ...
        // 会先排序 _dirtyElements,排序规则是先判断深度
        // 深度浅的在前,深度深的在后。
        // 深度相同的判断是否有脏标记,有脏标记的在前,没有的在后
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        int dirtyCount = _dirtyElements.length;
        int index = 0;
        // 依次遍历 _dirtyElements,调用 rebuild()
        while (index < dirtyCount) {
          // 这里调用 rebuild
          _dirtyElements[index].rebuild();
          index += 1;
          // ...
        }
      }

}
复制代码

到这里为止, markNeedsBuild(..)会让 element 自身加入到 BuildOwner 的  _dirtyElements 列表中。

根据 scheduleBuildFor 的注释,这个 _dirtyElement 是在 WidgetsBinding.drawFrame 触发 BuildOwner.buildScope(..)的时候消费的。

接下来我们来看下 buildScope(..) 函数,这个函数会先把  _dirtyElements 排下序,排序好之后依次调用 element.rebuild()

buildScope会调用 element 的 rebuild()

现在我们已经知道 Element.markNeedsBuild  element 变脏,然后在下一帧 drawFrame时候 rebuild

StatefulWidget中的setState()

那么 Element.markNeedsBuild 什么时候会触发呢?它触发的地方比较多,但是最常见的地方就是我们 StatefulWidget 中的 setState((){}):

abstract class State<T extends StatefulWidget> {
  @protected
  void setState(VoidCallback fn) {
    //...
    // 这里最后调用了 state 对应的 element,然后 markNeedsBuild
    _element.markNeedsBuild();
  }
}
复制代码

以上源码的时序图如下:

StatelessWidget创建流程.png

总结

了解了StatelessWidget的源码, 我们可以知道, StatelessWidget实现的build(BuildContext context),这个context就是StatelessElement对象。

StatefulWidget组件中, setState时会触发Element.markNeedsBuild

参考资料

Flutter BuildContext 探究

Builder 构造器与 BuildContext 认知

谈谈 Flutter 的 build

猜你喜欢

转载自juejin.im/post/7033235647091441672
今日推荐